Voici un lien vers un document d'un algorithme qui produit les valeurs et le code que je vois avec Visual Studio (dans la plupart des cas) et que je suppose est toujours utilisé dans GCC pour la division d'un entier variable par un entier constant.
http://gmplib.org/~tege/divcnst-pldi94.pdf
Dans l'article, un uword a N bits, un udword a 2N bits, n = numérateur = dividende, d = dénominateur = diviseur, ℓ est initialement défini sur ceil (log2 (d)), shpre est pré-décalage (utilisé avant multiplication ) = e = nombre de bits de fin à zéro dans d, shpost est post-shift (utilisé après multiplication), prec est précision = N - e = N - shpre. Le but est d'optimiser le calcul de n / d en utilisant un pré-décalage, une multiplication et un post-décalage.
Faites défiler jusqu'à la figure 6.2, qui définit comment un multiplicateur udword (la taille maximale est N + 1 bits), est généré, mais n'explique pas clairement le processus. Je vais l'expliquer ci-dessous.
Les figures 4.2 et 6.2 montrent comment le multiplicateur peut être réduit à un multiplicateur N bits ou moins pour la plupart des diviseurs. L'équation 4.5 explique comment la formule utilisée pour traiter les multiplicateurs N + 1 bits dans les figures 4.1 et 4.2 a été dérivée.
Dans le cas du X86 moderne et d'autres processeurs, le temps de multiplication est fixe, donc le pré-décalage n'aide pas sur ces processeurs, mais il aide toujours à réduire le multiplicateur de N + 1 bits à N bits. Je ne sais pas si GCC ou Visual Studio ont éliminé le pré-décalage pour les cibles X86.
Revenons à la figure 6.2. Le numérateur (dividende) pour mlow et mhigh ne peut être plus grand qu'un udword uniquement lorsque le dénominateur (diviseur)> 2 ^ (N-1) (lorsque ℓ == N => mlow = 2 ^ (2N)), dans ce cas, le le remplacement optimisé de n / d est une comparaison (si n> = d, q = 1, sinon q = 0), donc aucun multiplicateur n'est généré. Les valeurs initiales de mlow et mhigh seront N + 1 bits, et deux divisions udword / uword peuvent être utilisées pour produire chaque valeur N + 1 bits (mlow ou mhigh). Utiliser X86 en mode 64 bits comme exemple:
; upper 8 bytes of dividend = 2^(ℓ) = (upper part of 2^(N+ℓ))
; lower 8 bytes of dividend for mlow = 0
; lower 8 bytes of dividend for mhigh = 2^(N+ℓ-prec) = 2^(ℓ+shpre) = 2^(ℓ+e)
dividend dq 2 dup(?) ;16 byte dividend
divisor dq 1 dup(?) ; 8 byte divisor
; ...
mov rcx,divisor
mov rdx,0
mov rax,dividend+8 ;upper 8 bytes of dividend
div rcx ;after div, rax == 1
mov rax,dividend ;lower 8 bytes of dividend
div rcx
mov rdx,1 ;rdx:rax = N+1 bit value = 65 bit value
Vous pouvez tester cela avec GCC. Vous avez déjà vu comment j = i / 5 est géré. Jetez un œil à la façon dont j = i / 7 est géré (qui devrait être le cas du multiplicateur N + 1 bits).
Sur la plupart des processeurs actuels, la multiplication a un timing fixe, donc un pré-décalage n'est pas nécessaire. Pour X86, le résultat final est une séquence de deux instructions pour la plupart des diviseurs, et une séquence de cinq instructions pour des diviseurs comme 7 (afin d'émuler un multiplicateur N + 1 bits comme indiqué dans l'équation 4.5 et la figure 4.2 du fichier pdf). Exemple de code X86-64:
; rax = dividend, rbx = 64 bit (or less) multiplier, rcx = post shift count
; two instruction sequence for most divisors:
mul rbx ;rdx = upper 64 bits of product
shr rdx,cl ;rdx = quotient
;
; five instruction sequence for divisors like 7
; to emulate 65 bit multiplier (rbx = lower 64 bits of multiplier)
mul rbx ;rdx = upper 64 bits of product
sub rbx,rdx ;rbx -= rdx
shr rbx,1 ;rbx >>= 1
add rdx,rbx ;rdx = upper 64 bits of corrected product
shr rdx,cl ;rdx = quotient
; ...