Beim Schreiben einer optimierten ftol
Funktion habe ich ein sehr merkwürdiges Verhalten gefunden GCC 4.6.1
. Lassen Sie mich Ihnen zuerst den Code zeigen (zur Verdeutlichung habe ich die Unterschiede markiert):
fast_trunc_one, C:
int fast_trunc_one(int i) {
int mantissa, exponent, sign, r;
mantissa = (i & 0x07fffff) | 0x800000;
exponent = 150 - ((i >> 23) & 0xff);
sign = i & 0x80000000;
if (exponent < 0) {
r = mantissa << -exponent; /* diff */
} else {
r = mantissa >> exponent; /* diff */
}
return (r ^ -sign) + sign; /* diff */
}
fast_trunc_two, C:
int fast_trunc_two(int i) {
int mantissa, exponent, sign, r;
mantissa = (i & 0x07fffff) | 0x800000;
exponent = 150 - ((i >> 23) & 0xff);
sign = i & 0x80000000;
if (exponent < 0) {
r = (mantissa << -exponent) ^ -sign; /* diff */
} else {
r = (mantissa >> exponent) ^ -sign; /* diff */
}
return r + sign; /* diff */
}
Scheint gleich richtig? Nun, GCC ist anderer Meinung. Nach dem Kompilieren mit gcc -O3 -S -Wall -o test.s test.c
Dies ist die Assembly-Ausgabe:
fast_trunc_one, generiert:
_fast_trunc_one:
LFB0:
.cfi_startproc
movl 4(%esp), %eax
movl $150, %ecx
movl %eax, %edx
andl $8388607, %edx
sarl $23, %eax
orl $8388608, %edx
andl $255, %eax
subl %eax, %ecx
movl %edx, %eax
sarl %cl, %eax
testl %ecx, %ecx
js L5
rep
ret
.p2align 4,,7
L5:
negl %ecx
movl %edx, %eax
sall %cl, %eax
ret
.cfi_endproc
fast_trunc_two, generiert:
_fast_trunc_two:
LFB1:
.cfi_startproc
pushl %ebx
.cfi_def_cfa_offset 8
.cfi_offset 3, -8
movl 8(%esp), %eax
movl $150, %ecx
movl %eax, %ebx
movl %eax, %edx
sarl $23, %ebx
andl $8388607, %edx
andl $255, %ebx
orl $8388608, %edx
andl $-2147483648, %eax
subl %ebx, %ecx
js L9
sarl %cl, %edx
movl %eax, %ecx
negl %ecx
xorl %ecx, %edx
addl %edx, %eax
popl %ebx
.cfi_remember_state
.cfi_def_cfa_offset 4
.cfi_restore 3
ret
.p2align 4,,7
L9:
.cfi_restore_state
negl %ecx
sall %cl, %edx
movl %eax, %ecx
negl %ecx
xorl %ecx, %edx
addl %edx, %eax
popl %ebx
.cfi_restore 3
.cfi_def_cfa_offset 4
ret
.cfi_endproc
Das ist ein extrem Unterschied. Dies wird tatsächlich auch im Profil angezeigt, fast_trunc_one
ist etwa 30% schneller als fast_trunc_two
. Nun meine Frage: woran liegt das?
Zu Testzwecken habe ich ein Gist erstellt hier wo Sie den Quellcode einfach kopieren/einfügen und sehen können, ob Sie den Fehler auf anderen Systemen/Versionen von GCC reproduzieren können.
– orlp
20. April 2012 um 17:16 Uhr
Legen Sie die Testfälle in einem eigenen Verzeichnis ab. Kompilieren Sie sie mit
-S -O3 -da -fdump-tree-all
. Dadurch werden viele Momentaufnahmen der Zwischendarstellung erstellt. Gehen Sie sie nebeneinander durch (sie sind nummeriert), und Sie sollten in der Lage sein, die fehlende Optimierung im ersten Fall zu finden.– zol
20. April 2012 um 17:21 Uhr
Vorschlag zwei: Alle ändern
int
zuunsigned int
und sehen, ob der Unterschied verschwindet.– zol
20. April 2012 um 17:22 Uhr
Die beiden Funktionen scheinen etwas unterschiedliche Berechnungen durchzuführen. Während die Ergebnisse dieselben sein können, ist der Ausdruck
(r + shifted) ^ sign
ist nicht dasselbe wier + (shifted ^ sign)
. Ich denke, das verwirrt den Optimierer? FWIW, MSVC 2010 (16.00.40219.01) erzeugt Listen, die nahezu identisch sind: gist.github.com/2430454– DCoder
20. April 2012 um 17:26 Uhr
@DCoder: Oh verdammt! Das habe ich nicht bemerkt. Es ist jedoch nicht die Erklärung für den Unterschied. Lassen Sie mich die Frage mit einer neuen Version aktualisieren, in der dies ausgeschlossen ist.
– orlp
20. April 2012 um 17:36 Uhr