Beim Schreiben einer optimierten ftol
Funktion habe ich ein sehr merkwürdiges Verhalten festgestellt GCC 4.6.1
. Lassen Sie mich Ihnen zuerst den Code zeigen (aus Gründen der Klarheit 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 das gleiche richtig? Nun, GCC ist anderer Meinung. Nach dem Kompilieren ist gcc -O3 -S -Wall -o test.s test.c
dies 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 extremer Unterschied. Dies zeigt sich auch im Profil, fast_trunc_one
ist rund 30% schneller als fast_trunc_two
. Nun meine Frage: Was verursacht das?
-S -O3 -da -fdump-tree-all
. Dadurch werden viele Schnappschüsse der Zwischendarstellung erstellt. Gehen Sie sie nebeneinander durch (sie sind nummeriert), und Sie sollten im ersten Fall in der Lage sein, die fehlende Optimierung zu finden.
int
in unsigned int
und prüfen Sie, ob der Unterschied verschwindet.
(r + shifted) ^ sign
nicht derselbe wie r + (shifted ^ sign)
. Ich denke, das verwirrt den Optimierer? FWIW, MSVC 2010 (16.00.40219.01) erstellt Einträge, die fast identisch sind: gist.github.com/2430454