2015-03-02 61 views
1

下面是一个C函数来评估多项式:为什么-O1比-O2快10000次?

/* 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; 
} 

当我编译与海湾合作委员会和选项-O1和-02程序分开,我发现, -O1比-O2快很多。

平台详细信息:

  • i5-4600
  • Arch Linux的x86_64的内核3.18
  • GCC 4.9.2
  • 的gcc -o -O1 /tmp/a.out test.c的
  • 的gcc -o -02 /tmp/a.out test.c的

结果:

  • 当次= 100000ll,-O1即刻打印出结果,而-02需要0.36s
  • 当次= 1000000000ll,-O1打印结果0.28s,-02花费这么长时间,我没有完成测试

似乎-O1比-O2快大约10000倍。

当我测试它在Mac(铛-600.0.56),其结果是更加怪异:-O1时间不超过0.02秒,即使TIMES = 1000000000000000000ll

我已经测试下列的变化:

  • 使得G_A随机(元素是从1到10)
  • X = 19234(或某个其他数目)
  • 使用int代替长长整型

结果是一样的。

我试着看看汇编代码,看起来-O1调用poly函数,而-O2做内联优化。但内联应该使表现更好,不是吗?

是什么使这些巨大的差异?为什么-O1在铿锵声中可以让节目如此之快?是-O1做错了什么? (我不能检查结果,因为它太慢没有优化)

回答

0

有一件事,我跳出来是你溢出签名整数。这个行为在C中是未定义的。具体来说,int result将不能保持pow(2,9999)。我看不出具有未定义行为的基准测试代码的重点是什么?

+0

x = 1呢?我试图通过使用scanf来获得x,然后将它传递给poly调用,结果是一样的。我认为这不是主要问题...... – nnkken 2015-03-02 10:49:00

3

这里是main-O1汇编代码:

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 

虽然我不知道:

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(您可以通过添加-S选项GCC得到它)很多关于装配,很显然-O1只是调用poly一次,并将结果乘以100000(imulq $100000, %rdx, %rsi)。这是它如此之快的原因。

似乎gcc可以检测到poly是一个没有副作用的纯函数。 (这将是有趣的,如果我们有另一个线程修改g_apoly运行...)

在另一方面,-O2内联了poly功能,因此它没有机会检查poly作为一个纯函数。

我进一步做了一些研究:

我无法找到-O1该做纯函数用于核对实际的标志。

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

也许它需要结合在一起才能得到效果,但很难尝试所有的组合。

但是我找到了-O2这个标志,它使得效果消失,这是-finline-small-functions标志。国旗的名称解释了它自己。

相关问题