有一天,我遇到了一个奇怪的问题,使用GCC和'-Ofast'优化标志。使用' gcc -Ofast -o fib1 fib1.c'编译以下程序。
#include <stdio.h>
int f1(int n) {
if (n < 2) {
return n;
}
int a, b;
a = f1(n - 1);
b = f1(n - 2);
return a + b;
}
int main(){
printf("%d", f1(40));
}
测量执行时间时,结果为:
peter@host ~ $ time ./fib1
102334155
real 0m0.511s
user 0m0.510s
sys 0m0.000s
现在让我们在程序中引入一个全局变量,然后使用&#39; gcc -Ofast -o fib2 fib2.c&#39;再次编译。
#include <stdio.h>
int global;
int f1(int n) {
if (n < 2) {
return n;
}
int a, b;
a = f1(n - 1);
b = f1(n - 2);
global = 0;
return a + b;
}
int main(){
printf("%d", f1(40));
}
现在执行时间是:
peter@host ~ $ time ./fib2
102334155
real 0m0.265s
user 0m0.265s
sys 0m0.000s
新的全局变量没有做任何有意义的事情。但是,执行时间的差异很大。
除了问题(1)这种行为的原因是什么,如果(2)在不引入无意义变量的情况下可以实现最后的表现,也会很好。有什么建议吗?
由于 彼得
答案 0 :(得分:1)
我相信你会遇到一些非常聪明且非常奇怪的gcc(mis - ?)优化。这就是我研究这个问题的时候。
我修改了您的代码,使其在全局范围内拥有#ifdef G
:
$ cc -O3 -o foo foo.c && time ./foo
102334155
real 0m0.634s
user 0m0.631s
sys 0m0.001s
$ cc -O3 -DG -o foo foo.c && time ./foo
102334155
real 0m0.365s
user 0m0.362s
sys 0m0.001s
所以我有相同的奇怪性能差异。
如有疑问,请阅读生成的汇编程序。
$ cc -S -O3 -o foo.s -S foo.c
$ cc -S -DG -O3 -o foog.s -S foo.c
这里变得非常奇怪。通常我可以很容易地遵循gcc生成的代码。在这里生成的代码是不可理解的。什么应该是非常简单的递归和添加应该适合15-20指令,gcc扩展到数百条指令,在堆栈上有一系列移位,加法,减法,比较,分支和大数组。看起来它试图将一个或两个递归部分转换为迭代,然后展开该循环。有一件事让我感到震惊,非全局函数只有一个递归调用(第二个是来自main的调用):
$ grep 'call.*f1' foo.s | wc
2 4 18
虽然全球一个人有:
$ grep 'call.*f1' foog.s | wc
33 66 297
我受过教育(之前我见过很多次)猜? Gcc试图变得聪明,并且在其热情中理论上应该更容易优化生成更糟糕代码的功能,而对全局变量的写入使其充分混淆,以至于无法进行优化以至于它导致更好的代码。这种情况一直都在发生,gcc(以及其他编译器也让它们不单独使用)的许多优化都非常特定于它们使用的某些基准测试,并且在许多其他情况下可能无法生成更快的运行代码。事实上,根据经验,我只使用-O2,除非我非常仔细地对事情进行基准测试,看看-O3是否有意义。它经常没有。
如果您真的想进一步研究这个问题,我建议您阅读gcc文档,了解哪些优化功能已通过-O3
启用而不是-O2
(-O2
不执行此操作) ,然后一个接一个地尝试它们,直到找到导致这种行为的那个,并且优化应该是一个很好的暗示正在发生的事情。我准备这样做,但我没时间(必须做最后一分钟的圣诞购物)。
答案 1 :(得分:0)
在我的机器上(gcc (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010
)我得到了这个:
time ./fib1 0,36s user 0,00s system 98% cpu 0,364 total
time ./fib2 0,20s user 0,00s system 98% cpu 0,208 total
来自man gcc
:
-Ofast
无视严格的标准合规性。 -Ofast启用所有-O3优化。它还支持对所有符合标准的程序无效的优化。它打开-ffast-math和Fortran特定的-fno-protect-parens和-fstack-arrays。
不太安全的选择,让我们试试-O2
:
time ./fib1 0,38s user 0,00s system 99% cpu 0,377 total
time ./fib2 0,47s user 0,00s system 99% cpu 0,470 total
我认为,某些积极的优化未应用于fib1
,但已应用于fib2
。当我为-Ofast
切换-O2
时,某些优化未应用于fib2
,但已应用于fib1
。
让我们试试-O0
:
time ./fib1 0,81s user 0,00s system 99% cpu 0,812 total
time ./fib2 0,81s user 0,00s system 99% cpu 0,814 total
他们是平等的,没有优化 因此,在递归函数中引入全局变量一方面可以打破一些优化,另一方面可以改进其他优化。
答案 2 :(得分:0)
这是由第二版早期的内联限制引起的。因为带有全局变量的版本更多。这有力地表明,在这个特定的例子中,内联使得运行时性能更差。
使用-Ofast -fno-inline
编译两个版本,时间差异消失了。事实上,没有全局变量的版本运行得更快。
或者,只需使用__attribute__((noinline))
标记该功能。