我刚刚编译了以下 C 代码来测试gcc优化器(使用-O3标志),期望这两个函数最终会生成相同的汇编指令集:
int test1(int a, int b)
{
#define x (a*a*a+b)
#define y (a*b*a+3*b)
return x*x+x*y+y;
#undef x
#undef y
}
int test2(int a, int b)
{
int x = a*a*a+b;
int y = a*b*a+3*b;
return x*x+x*y+y;
}
但我很惊讶地发现它们生成了稍微不同的程序集,并且test1的执行时间(使用预处理程序而不是局部变量的代码)的速度要快一些。
我听说有人说编译器可以比人类更好地进行优化,并且你应该准确地告诉它你想要它做什么;男人,我猜他们不是在开玩笑。我认为编译器应该猜测程序员对局部变量的预期用途,并在必要时替换它们的使用......这是一个错误的假设吗?
在编写性能代码时,为了便于阅读而不是局部变量,最好使用预处理器定义吗?我知道它看起来很难看,但显然它确实有所作为,除非我遗漏了什么。
这是我使用“gcc test.c -O3 -S”获得的程序集。我的gcc版本是4.8.2;看起来大多数版本的gcc的程序集输出都是相同的,但由于某种原因不适用于4.7或4.8版本
test1
movl %edi, %eax
movl %edi, %edx
leal (%rsi,%rsi,2), %ecx
imull %edi, %eax
imull %esi, %edx
imull %edi, %eax
imull %edi, %edx
addl %esi, %eax
addl %ecx, %edx
leal (%rax,%rdx), %ecx
imull %ecx, %eax
addl %edx, %eax
ret
test2
movl %edi, %eax
leal (%rsi,%rsi,2), %edx
imull %edi, %eax
imull %edi, %eax
leal (%rax,%rsi), %ecx
movl %edi, %eax
imull %esi, %eax
imull %edi, %eax
addl %eax, %edx
leal (%rcx,%rdx), %eax
imull %ecx, %eax
addl %edx, %eax
ret
答案 0 :(得分:4)
在godbolt尝试代码我使用GCC获得两个函数的相同汇编,即使使用-O设置也是如此。只有省略-O标志我得到不同的结果。这真的是预期的,因为代码很容易优化。
这是使用带有-O标志的gcc 4.4.7生成的程序集。如你所见,它们完全相同。
test1(int, int):
movl %edi, %eax
imull %edi, %eax
imull %eax, %edi
addl $3, %eax
imull %esi, %eax
addl %esi, %edi
leal (%rax,%rdi), %edx
imull %edi, %edx
leal (%rdx,%rax), %eax
ret
test2(int, int):
movl %edi, %eax
imull %edi, %eax
imull %eax, %edi
addl $3, %eax
imull %esi, %eax
addl %esi, %edi
leal (%rax,%rdi), %edx
imull %edi, %edx
leal (%rdx,%rax), %eax
ret
答案 1 :(得分:1)
test1
”快速复制您的搜索结果“test2
。结果不应该相同。 pre 处理器在之前对源进行操作(转换),编译器实际上使用任何选项编译它。
例如,假设您使用的是GNU编译器并且上面的源存储在文件gcc -E main.c
中,您可以通过运行main.c
来检查预处理器的结果。相关部分成为:
int test1(int a, int b)
{
return (a*a*a+b)*(a*a*a+b)+(a*a*a+b)*(a*b*a+3*b)+(a*b*a+3*b);
}
int test2(int a, int b)
{
int x = a*a*a+b;
int y = a*b*a+3*b;
return x*x+x*y+y;
}
显然,第一个版本使用的数学运算大约是第二个版本的两倍。然后编译器及其优化器发挥作用......
(注意:理想情况下,您可以分析汇编程序代码生成的CPU周期数。使用例如gcc -S main.c
并查看main.s
;您可能知道。版本2应该“赢”情况)。
为了比较我们的结果,您应该发布您的测试代码。测试时,您需要平均CPU的短期波动和时间粒度限制。因此,您可能会在相同代码的循环中运行。
int i=100000000;
while (--i>0) {
int r;
r = test1(3, 4);
}
如果没有优化工具,test1
显然比test2
慢了约20%。
但是,优化器也会分析调用代码,并且可以使用相同的参数或使用未使用的变量(在这种情况下为r
)调用多个调用。
因此,您必须欺骗编译器以有效地进行调用,类似
int r = 0;
while (--i>0) {
r += test1(3, i);
}
当我尝试这种方法时,我获得了相同的运行时百分比精度。即有时time1
更快,有时time2
更快,当我多次重复比较时。
您应该查看优化器文档,了解在测试中需要智能的优化选项。
我确认了@Ville Krumlinde所说的内容:即使使用-O
级别优化(桌面上的gcc 4.4.7),我也会得到相同的汇编输出代码。代码只包含汇编程序中的9个操作,这让我相信优化程序“足够了解”代数优化以简化公式。
因此,您可能会被测试框架的假冒优化效果所取代。