【问题标题】:Does gcc automatically "unroll" if-statements?gcc 会自动“展开”if 语句吗?
【发布时间】:2011-02-16 14:42:38
【问题描述】:

假设我有一个如下所示的循环:

for(int i = 0; i < 10000; i++) {
    /* Do something computationally expensive */
    if (i < 200 && !(i%20)) {
        /* Do something else */
    }
}

其中一些琐碎的任务被卡在只运行几次的 if 语句后面。 我一直听说“循环中的 if 语句很慢!”因此,为了(略微)提高性能,我将循环拆分为:

for(int i = 0; i < 200; i++) {
    /* Do something computationally expensive */
    if (!(i%20)) {
        /* Do something else */
    }
}

for(int i = 200; i < 10000; i++) {
    /* Do something computationally expensive */
}

gcc(带有适当的标志,如 -O3)会自动将一个循环分成两个,还是只是展开以减少迭代次数?

【问题讨论】:

  • 我当然记得 GCC 邮件列表中对此的讨论——至少有一些支持。通过检查汇编程序输出或-da,它是否真的会在您的情况下执行它可能最容易验证;它可能只是展开了 0 案例或最大循环案例,我不记得了。但是,在您的情况下(对于 200-10000,它将是:测试 i
  • 你可以通过检查每个迭代器的 i 的 lsb 来优化(和混淆)代码,因为奇数不能被 20 整除。如果 i
  • 您总是听说过“如果循环中的语句很慢”吗?你是从哪里听来的?只是好奇。

标签: c optimization gcc unroll


【解决方案1】:

为什么不直接反汇编程序并亲自看看呢?但我们走了。这是测试程序:

int main() {
    int sum = 0;
    int i;
    for(i = 0; i < 10000; i++) {
        if (i < 200 && !(i%20)) {
            sum += 0xC0DE;
        }
        sum += 0xCAFE;
    }
    printf("%d\n", sum);
    return 0;
}

这是使用 gcc 4.3.3 和 -o3 编译的反汇编代码的有趣部分:

0x08048404 <main+20>:   xor    ebx,ebx
0x08048406 <main+22>:   push   ecx
0x08048407 <main+23>:   xor    ecx,ecx
0x08048409 <main+25>:   sub    esp,0xc
0x0804840c <main+28>:   lea    esi,[esi+eiz*1+0x0]
0x08048410 <main+32>:   cmp    ecx,0xc7
0x08048416 <main+38>:   jg     0x8048436 <main+70>
0x08048418 <main+40>:   mov    eax,ecx
0x0804841a <main+42>:   imul   esi
0x0804841c <main+44>:   mov    eax,ecx
0x0804841e <main+46>:   sar    eax,0x1f
0x08048421 <main+49>:   sar    edx,0x3
0x08048424 <main+52>:   sub    edx,eax
0x08048426 <main+54>:   lea    edx,[edx+edx*4]
0x08048429 <main+57>:   shl    edx,0x2
0x0804842c <main+60>:   cmp    ecx,edx
0x0804842e <main+62>:   jne    0x8048436 <main+70>
0x08048430 <main+64>:   add    ebx,0xc0de
0x08048436 <main+70>:   add    ecx,0x1
0x08048439 <main+73>:   add    ebx,0xcafe
0x0804843f <main+79>:   cmp    ecx,0x2710
0x08048445 <main+85>:   jne    0x8048410 <main+32>
0x08048447 <main+87>:   mov    DWORD PTR [esp+0x8],ebx
0x0804844b <main+91>:   mov    DWORD PTR [esp+0x4],0x8048530
0x08048453 <main+99>:   mov    DWORD PTR [esp],0x1
0x0804845a <main+106>:  call   0x8048308 <__printf_chk@plt>

所以我们看到,对于这个特定的例子,不,它没有。我们只有一个循环,从 main+32 开始,到 main+85 结束。如果您在阅读汇编代码时遇到问题 ecx = i; ebx = 总和。

但您的里程可能会有所不同 - 谁知道针对这种特殊情况使用了什么启发式方法,所以您必须编译您想到的代码,看看更长/更复杂的计算如何影响优化器。

尽管在任何现代 CPU 上,分支预测器在如此简单的代码上都会做得很好,因此在任何一种情况下您都不会看到太多的性能损失。如果您的计算密集型代码需要数十亿个周期,那么一些错误预测可能会造成什么性能损失?

【讨论】:

  • 啊——非常感谢!我从来不知道如何分析反汇编代码。
猜你喜欢
  • 2017-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多