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

时间:2015-03-02 10:09:26

标签: c compiler-optimization

下面是评估多项式的​​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
  • 建立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)时,结果更加奇怪:即使TIMES = 1000000000000000000ll,-O1也不会超过0.02秒

我测试了以下更改:

  • 使g_a随机(元素从1到10)
  • x = 19234(或其他一些数字)
  • 使用int而不是long long int

结果是一样的。

我试着查看汇编代码,似乎-O1调用poly函数,而-O2执行内联优化。但内联应该使性能更好,不是吗?

是什么让这些巨大的差异?为什么-O1 on clang可以使程序如此之快? -O1做错了吗? (我无法检查结果,因为没有优化它太慢了)

2 个答案:

答案 0 :(得分:5)

以下是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标志。国旗的名称解释了自己。

答案 1 :(得分:0)

对我而言,有一件事就是你已经溢出了有符号的整数。这个行为在C中是未定义的。具体来说,int result无法保持战力(2,9999)。我不明白用未定义的行为对代码进行基准测试的重点是什么?