在编写优化ftol函数时,我发现GCC 4.6.1. 让我先向您展示代码(为清楚起见,我标记了差异):
ftol
GCC 4.6.1
快速截断,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 */ }
好像一样吧?那么GCC不同意。编译后gcc -O3 -S -Wall -o test.s test.c是汇编输出:
gcc -O3 -S -Wall -o test.s test.c
fast_trunc_one,生成:
_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,生成:
_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
这是一个 极端 的区别。这实际上也显示在配置文件上,fast_trunc_one比fast_trunc_two. 现在我的问题是:是什么原因造成的?
fast_trunc_one
fast_trunc_two
更新以与 OP 的编辑同步
通过修改代码,我设法了解 GCC 如何优化第一种情况。
在我们了解它们为什么如此不同之前,首先我们必须了解 GCC 是如何优化fast_trunc_one().
fast_trunc_one()
信不信由你,fast_trunc_one()正在对此进行优化:
int fast_trunc_one(int i) { int mantissa, exponent; mantissa = (i & 0x07fffff) | 0x800000; exponent = 150 - ((i >> 23) & 0xff); if (exponent < 0) { return (mantissa << -exponent); /* diff */ } else { return (mantissa >> exponent); /* diff */ } }
这会产生与原始程序完全相同的程序集fast_trunc_one()- 注册名称和所有内容。
请注意,在程序集中没有xors for fast_trunc_one()。这就是给我的。
xor
步骤1: sign = -sign
sign = -sign
首先,让我们看一下sign变量。由于sign = i & 0x80000000;,只有两个可能的值sign可以取:
sign
sign = i & 0x80000000;
sign = 0
sign = 0x80000000
现在认识到,在这两种情况下,sign == -sign. 因此,当我将原始代码更改为:
sign == -sign
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; } else { r = mantissa >> exponent; } return (r ^ sign) + sign; }
它产生与原始的完全相同的程序集fast_trunc_one()。我会省去你的程序集,但它是相同的 - 注册名称和所有。
第 2 步: 数学简化:x + (y ^ x) = y
x + (y ^ x) = y
sign只能取两个值之一,0或0x80000000。
0
0x80000000
x = 0
x = 0x80000000
因此,x + (y ^ x)减少到y。代码简化为:
x + (y ^ x)
y
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); } else { r = (mantissa >> exponent); } return r; }
同样,这将编译为完全相同的程序集 - 注册名称和所有。
上述版本最终简化为:
这几乎正是 GCC 在程序集中生成的。
那么为什么编译器不优化fast_trunc_two()相同的东西呢?
fast_trunc_two()
其中的关键部分fast_trunc_one()是x + (y ^ x) = y优化。在表达式fast_trunc_two()中x + (y ^ x)被拆分跨分支。
我怀疑这可能足以让 GCC 不进行这种优化。(它需要将^ -sign分支吊出并将其合并到r + sign最后。)
^ -sign
r + sign
例如,这会产生与以下相同的程序集fast_trunc_one():
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) + sign; /* diff */ } else { r = ((mantissa >> exponent) ^ -sign) + sign; /* diff */ } return r; /* diff */ }