为什么没有编译器能够优化此代码?
考虑下面的C代码(假设80位long double
)(注意,我知道memcmp
,这只是一个实验):
enum { sizeOfFloat80=10 }; // NOTE: sizeof(long double) != sizeOfFloat80
_Bool sameBits1(long double x, long double y)
{
for(int i=0;i<sizeOfFloat80;++i)
if(((char*)&x)[i]!=((char*)&y)[i])
return 0;
return 1;
}
我检查过的所有编译器(gcc,clang,icc on gcc.godbolt.org)都会生成类似的代码,这里有一个带有选项的gcc示例-O3 -std=c11 -fomit-frame-pointer -m32
:
sameBits1:
movzx eax, BYTE PTR [esp+16]
cmp BYTE PTR [esp+4], al
jne .L11
movzx eax, BYTE PTR [esp+17]
cmp BYTE PTR [esp+5], al
jne .L11
movzx eax, BYTE PTR [esp+18]
cmp BYTE PTR [esp+6], al
jne .L11
movzx eax, BYTE PTR [esp+19]
cmp BYTE PTR [esp+7], al
jne .L11
movzx eax, BYTE PTR [esp+20]
cmp BYTE PTR [esp+8], al
jne .L11
movzx eax, BYTE PTR [esp+21]
cmp BYTE PTR [esp+9], al
jne .L11
movzx eax, BYTE PTR [esp+22]
cmp BYTE PTR [esp+10], al
jne .L11
movzx eax, BYTE PTR [esp+23]
cmp BYTE PTR [esp+11], al
jne .L11
movzx eax, BYTE PTR [esp+24]
cmp BYTE PTR [esp+12], al
jne .L11
movzx eax, BYTE PTR [esp+25]
cmp BYTE PTR [esp+13], al
sete al
ret
.L11:
xor eax, eax
ret
这看起来很丑陋,在每个字节上都有分支,事实上似乎根本没有被优化过(但至少循环被展开)。 很容易看出,这可以优化为相当于以下代码的代码(并且一般用于较大数据以使用更大的步幅):
#include <string.h>
_Bool sameBits2(long double x, long double y)
{
long long X=0; memcpy(&X,&x,sizeof x);
long long Y=0; memcpy(&Y,&y,sizeof y);
short Xhi=0; memcpy(&Xhi,sizeof x+(char*)&x,sizeof Xhi);
short Yhi=0; memcpy(&Yhi,sizeof y+(char*)&y,sizeof Yhi);
return X==Y && Xhi==Yhi;
}
这段代码现在得到了更好的编译结果:
sameBits2:
sub esp, 20
mov edx, DWORD PTR [esp+36]
mov eax, DWORD PTR [esp+40]
xor edx, DWORD PTR [esp+24]
xor eax, DWORD PTR [esp+28]
or edx, eax
movzx eax, WORD PTR [esp+48]
sete dl
cmp WORD PTR [esp+36], ax
sete al
add esp, 20
and eax, edx
ret
所以我的问题是:为什么三种编译器都不能做这种优化? 它在C代码中看到的东西很少见?
首先,它无法进行这种优化,因为您通过重新解释内存过量来彻底混淆了代码的含义。 像这样的代码正好让编译器对“我不知道这是什么,但如果这就是你想要的,那就是你会得到的”。 为什么你期望编译器甚至费心去改变内存类型的解释重新解释为另一种内存重新解释(!)对我来说完全不清楚。
其次,理论上可以做到这一点,但它的重点可能不是很高。 请记住,代码优化通常是通过模式匹配算法完成的,而不是通过某种AI来完成的,而这仅仅是它所识别的模式之一。
大多数情况下,您的手动尝试执行代码的低级优化将会使编译器无法做到这一点。 如果你想自己优化它,然后一路走。 不要指望能够启动,然后将其交给编译器来为您完成工作。
比较两个long double
值x
和y
可以非常容易地完成: x == y
。 如果你想要比较内存比特,你可能只需在编译器中使用memcmp
就可以使编译器的工作变得更加简单,该编译器固有地知道memcmp
是什么(内建的,内部函数)。
上一篇: Why no compiler appears able to optimize this code?
下一篇: Why is (a*b != 0) faster than (a != 0 && b != 0) in Java?