gpt4 book ai didi

c - 为什么-O1比-O2快10000倍?

转载 作者:行者123 更新时间:2023-12-01 20:25:06 25 4
gpt4 key购买 nike

下面是一个计算多项式的 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
  • 海湾合作委员会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.28秒内打印结果,-O2花了很长时间,以至于我没有完成测试

看来 -O1 比 -O2 快大约 10000 倍。

当我在 Mac(clang-600.0.56)上测试时,结果更奇怪:即使 TIMES = 1000000000000000000ll,-O1 也不会超过 0.02s

我测试了以下更改:

  • 使 g_a 随机(元素从 1 到 10)
  • x = 19234(或其他数字)
  • 使用 int 代替 long long int

结果是一样的。

我尝试查看汇编代码,似乎-O1正在调用poly函数,而-O2则进行内联优化。但内联应该会让性能更好,不是吗?

是什么造成了这些巨大的差异?为什么 clang 上的 -O1 可以使程序运行得这么快? -O1 做错了什么吗? (我无法检查结果,因为未经优化速度太慢)

最佳答案

这是-O1main的汇编代码:(可以通过在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是一个没有副作用的纯函数。 (如果我们有另一个线程在 poly 运行时修改 g_a,那将会很有趣...)

另一方面,-O2 内联了 poly 函数,因此它没有机会将 poly 作为纯函数进行检查。

我进一步做了一些研究:

我找不到执行纯函数检查的 -O1 使用的实际标志。

我已分别尝试了 gcc -Q -O1 --help=optimizers 列出的所有标志,但没有一个具有效果。

也许需要将标志组合在一起才能达到效果,但尝试所有组合是非常困难的。

但是我发现了-O2使用的使效果消失的标志,即-finline-small-functions标志。旗帜的名称不言而喻。

关于c - 为什么-O1比-O2快10000倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28807120/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com