预处理器定义VS局部变量,速度差

时间:2015-04-22 07:09:12

标签: c gcc optimization c-preprocessor

我刚刚编译了以下 C 代码来测试gcc优化器(使用-O3标志),期望这两个函数最终会生成相同的汇编指令集:

int test1(int a, int b)
{
#define x (a*a*a+b)
#define y (a*b*a+3*b)
        return x*x+x*y+y;
#undef x
#undef y
}

int test2(int a, int b)
{
        int x = a*a*a+b;
        int y = a*b*a+3*b;
        return x*x+x*y+y;
}

但我很惊讶地发现它们生成了稍微不同的程序集,并且test1的执行时间(使用预处理程序而不是局部变量的代码)的速度要快一些。

我听说有人说编译器可以比人类更好地进行优化,并且你应该准确地告诉它你想要它做什么;男人,我猜他们不是在开玩笑。我认为编译器应该猜测程序员对局部变量的预期用途,并在必要时替换它们的使用......这是一个错误的假设吗?

在编写性能代码时,为了便于阅读而不是局部变量,最好使用预处理器定义吗?我知道它看起来很难看,但显然它确实有所作为,除非我遗漏了什么。

这是我使用“gcc test.c -O3 -S”获得的程序集。我的gcc版本是4.8.2;看起来大多数版本的gcc的程序集输出都是相同的,但由于某种原因不适用于4.7或4.8版本

test1
        movl    %edi, %eax
        movl    %edi, %edx
        leal    (%rsi,%rsi,2), %ecx
        imull   %edi, %eax
        imull   %esi, %edx
        imull   %edi, %eax
        imull   %edi, %edx
        addl    %esi, %eax
        addl    %ecx, %edx
        leal    (%rax,%rdx), %ecx
        imull   %ecx, %eax
        addl    %edx, %eax
        ret

test2
        movl    %edi, %eax
        leal    (%rsi,%rsi,2), %edx
        imull   %edi, %eax
        imull   %edi, %eax
        leal    (%rax,%rsi), %ecx
        movl    %edi, %eax
        imull   %esi, %eax
        imull   %edi, %eax
        addl    %eax, %edx
        leal    (%rcx,%rdx), %eax
        imull   %ecx, %eax
        addl    %edx, %eax
        ret

2 个答案:

答案 0 :(得分:4)

godbolt尝试代码我使用GCC获得两个函数的相同汇编,即使使用-O设置也是如此。只有省略-O标志我得到不同的结果。这真的是预期的,因为代码很容易优化。

这是使用带有-O标志的gcc 4.4.7生成的程序集。如你所见,它们完全相同。

test1(int, int):
    movl    %edi, %eax
    imull   %edi, %eax
    imull   %eax, %edi
    addl    $3, %eax
    imull   %esi, %eax
    addl    %esi, %edi
    leal    (%rax,%rdi), %edx
    imull   %edi, %edx
    leal    (%rdx,%rax), %eax
    ret
test2(int, int):
    movl    %edi, %eax
    imull   %edi, %eax
    imull   %eax, %edi
    addl    $3, %eax
    imull   %esi, %eax
    addl    %esi, %edi
    leal    (%rax,%rdi), %edx
    imull   %edi, %edx
    leal    (%rdx,%rax), %eax
    ret

答案 1 :(得分:1)

答案是双重的:

  1. 您对相同结果的陈述是一种误解
  2. 我无法以“test1”快速复制您的搜索结果“test2
  3. 预处理器误解

    结果不应该相同。 pre 处理器在之前对源进行操作(转换),编译器实际上使用任何选项编译它。

    例如,假设您使用的是GNU编译器并且上面的源存储在文件gcc -E main.c中,您可以通过运行main.c来检查预处理器的结果。相关部分成为:

    int test1(int a, int b)
    {
      return (a*a*a+b)*(a*a*a+b)+(a*a*a+b)*(a*b*a+3*b)+(a*b*a+3*b);
    }
    
    int test2(int a, int b)
    {
      int x = a*a*a+b;
      int y = a*b*a+3*b;
      return x*x+x*y+y;
    }
    

    显然,第一个版本使用的数学运算大约是第二个版本的两倍。然后编译器及其优化器发挥作用......

    (注意:理想情况下,您可以分析汇编程序代码生成的CPU周期数。使用例如gcc -S main.c并查看main.s;您可能知道。版本2应该“赢”情况)。

    运行时测试和优化

    为了比较我们的结果,您应该发布您的测试代码。测试时,您需要平均CPU的短期波动和时间粒度限制。因此,您可能会在相同代码的循环中运行。

    int i=100000000;
    while (--i>0) {
        int r;
        r = test1(3, 4);
        }
    

    如果没有优化工具,test1显然比test2慢了约20%。

    但是,优化器也会分析调用代码,并且可以使用相同的参数或使用未使用的变量(在这种情况下为r)调用多个调用。

    因此,您必须欺骗编译器以有效地进行调用,类似

    int r = 0;
    while (--i>0) {
        r += test1(3, i);
        }
    

    当我尝试这种方法时,我获得了相同的运行时百分比精度。即有时time1更快,有时time2更快,当我多次重复比较时。

    您应该查看优化器文档,了解在测试中需要智能的优化选项。

    我确认了@Ville Krumlinde所说的内容:即使使用-O级别优化(桌面上的gcc 4.4.7),我也会得到相同的汇编输出代码。代码只包含汇编程序中的9个操作,这让我相信优化程序“足够了解”代数优化以简化公式。

    因此,您可能会被测试框架的假冒优化效果所取代。