以下是用于评估多项式的C函数:
/* Calculate a0 + a1*x + a2*x^2 + ... + an*x^n */
/* from CSAPP Ex.5.5, modified to integer version */
int poly(int a[], int x, int degree) {
long int i;
int result = a[0];
int xpwr = x;
for (i = 1; i <= degree; ++i) {
result += a[i]*xpwr;
xpwr *= x;
}
return result;
}
还有一个主要功能:
#define TIMES 100000ll
int main(void) {
long long int i;
unsigned long long int result = 0;
for (i = 0; i < TIMES; ++i) {
/* g_a is an int[10000] global variable with all elements equals to 1 */
/* x = 2, i.e. evaluate 1 + 2 + 2^2 + ... + 2^9999 */
result += poly(g_a, 2, 9999);
}
printf("%lld\n", result);
return 0;
}
当我分别使用GCC和选项-O1和-O2编译程序时,我发现-O1比-O2更快。
平台详细信息:
i5-4600
具有内核3.18的Arch Linux x86_64
GCC 4.9.2
gcc -O1 -o /tmp/a.out test.c
gcc -O2 -o /tmp/a.out test.c
结果:
当TIMES = 100000ll时,-O1立即打印结果,而-O2需要0.36s
当TIMES = 1000000000ll时,-O1在0.28s内打印结果,-O2花费的时间太长,以至于我没有完成测试
看来-O1比-O2快大约10000倍。
当我在Mac(clang-600.0.56)上进行测试时,结果更加奇怪:-O1花费的时间不超过0.02s,即使TIMES = 1000000000000000000ll
我已经测试了以下更改:
使g_a随机(元素从1到10)
x = 19234(或其他一些数字)
使用int代替long long int
结果是一样的。
我尝试查看汇编代码,似乎-O1正在调用poly函数,而-O2进行内联优化。但是内联应该使性能更好,不是吗?
是什么使这些巨大的差异?为什么在c上使用-O1可以使程序如此之快? -O1做错了吗? (我无法检查结果,因为如果没有优化,结果太慢了)
最佳答案
这是main
的-O1
汇编代码:(您可以通过在gcc中添加-S
选项来获得它)
main:
.LFB12:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $9999, %edx
movl $2, %esi
movl $g_a, %edi
call poly
movslq %eax, %rdx
movl $100000, %eax
.L6:
subq $1, %rax
jne .L6
imulq $100000, %rdx, %rsi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
对于
-O2
:main:
.LFB12:
.cfi_startproc
movl g_a(%rip), %r9d
movl $100000, %r8d
xorl %esi, %esi
.p2align 4,,10
.p2align 3
.L8:
movl $g_a+4, %eax
movl %r9d, %ecx
movl $2, %edx
.p2align 4,,10
.p2align 3
.L7:
movl (%rax), %edi
addq $4, %rax
imull %edx, %edi
addl %edx, %edx
addl %edi, %ecx
cmpq $g_a+40000, %rax
jne .L7
movslq %ecx, %rcx
addq %rcx, %rsi
subq $1, %r8
jne .L8
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC1, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
尽管我对汇编的了解不多,但是很明显
-O1
仅调用一次poly
,然后将结果乘以100000(imulq $100000, %rdx, %rsi
)。这就是它这么快的原因。看来gcc可以检测到
poly
是纯函数,没有副作用。 (如果在g_a
运行时有另一个线程修改poly
,这将很有趣。)另一方面,
-O2
内联了poly
函数,因此没有机会将poly
检查为纯函数。我进一步做了一些研究:
我找不到执行纯功能检查的
-O1
使用的实际标志。我已经分别尝试了
gcc -Q -O1 --help=optimizers
列出的所有标志,但是没有一个起作用。可能需要将标志组合在一起才能获得效果,但是很难尝试所有组合。
但是我发现
-O2
使用的标志使效果消失,这是-finline-small-functions
标志。标志的名称说明自己。关于c - 为什么-O1比-O2快10000倍?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28807120/