Hier ist ein Link zu einem Dokument eines Algorithmus, der die Werte und den Code erzeugt, die ich mit Visual Studio sehe (in den meisten Fällen) und von denen ich annehme, dass sie in GCC immer noch zur Division einer variablen Ganzzahl durch eine konstante Ganzzahl verwendet werden.
http://gmplib.org/~tege/divcnst-pldi94.pdf
In dem Artikel hat ein U-Wort N Bits, ein U-Wort hat 2 N Bits, n = Zähler = Dividende, d = Nenner = Divisor, ℓ wird anfänglich auf Ceil gesetzt (log2 (d)), shpre ist Pre-Shift (wird vor dem Multiplizieren verwendet ) = e = Anzahl der nachgestellten Nullbits in d, shpost ist post-shift (wird nach Multiplikation verwendet), prec ist präzise = N - e = N - shpre. Ziel ist es, die Berechnung von n / d mithilfe von Pre-Shift, Multiplikation und Post-Shift zu optimieren.
Scrollen Sie nach unten zu Abbildung 6.2, in der definiert ist, wie ein udword-Multiplikator (maximale Größe ist N + 1 Bit) generiert wird, der Vorgang jedoch nicht klar erläutert wird. Ich werde das unten erklären.
Abbildung 4.2 und Abbildung 6.2 zeigen, wie der Multiplikator für die meisten Teiler auf ein N-Bit- oder weniger-Multiplikator reduziert werden kann. Gleichung 4.5 erklärt, wie die Formel für den Umgang mit N + 1-Bit-Multiplikatoren in Abbildung 4.1 und 4.2 abgeleitet wurde.
Bei modernen X86- und anderen Prozessoren ist die Multiplikationszeit festgelegt, sodass die Vorverschiebung bei diesen Prozessoren nicht hilfreich ist, der Multiplikator jedoch von N + 1 Bit auf N Bit reduziert werden kann. Ich weiß nicht, ob GCC oder Visual Studio die Vorverschiebung für X86-Ziele eliminiert haben.
Zurück zu Abbildung 6.2. Der Zähler (Dividende) für mlow und mhigh kann nur dann größer als ein udword sein, wenn der Nenner (Divisor)> 2 ^ (N-1) (wenn ℓ == N => mlow = 2 ^ (2N)) ist, in diesem Fall der Ein optimierter Ersatz für n / d ist ein Vergleich (wenn n> = d, q = 1, sonst q = 0), sodass kein Multiplikator generiert wird. Die Anfangswerte von mlow und mhigh sind N + 1 Bit, und zwei udword / uword-Teilungen können verwendet werden, um jeden N + 1-Bit-Wert (mlow oder mhigh) zu erzeugen. Verwenden von X86 im 64-Bit-Modus als Beispiel:
; 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
Sie können dies mit GCC testen. Sie haben bereits gesehen, wie mit j = i / 5 umgegangen wird. Schauen Sie sich an, wie mit j = i / 7 umgegangen wird (dies sollte der N + 1-Bit-Multiplikatorfall sein).
Bei den meisten aktuellen Prozessoren hat Multiplizieren ein festes Timing, sodass keine Vorverschiebung erforderlich ist. Für X86 ist das Endergebnis eine Zwei-Befehlsfolge für die meisten Teiler und eine Fünf-Befehlsfolge für Teiler wie 7 (um einen N + 1-Bit-Multiplikator zu emulieren, wie in Gleichung 4.5 und Abbildung 4.2 der PDF-Datei gezeigt). Beispiel X86-64 Code:
; 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
; ...