强制/说服/欺骗 GCC 展开 _Longer_ 循环?

Posted

技术标签:

【中文标题】强制/说服/欺骗 GCC 展开 _Longer_ 循环?【英文标题】:Force/Convince/Trick GCC into Unrolling _Longer_ Loops? 【发布时间】:2014-11-10 19:06:35 【问题描述】:

如何说服 GCC 展开一个迭代次数已知但很大的循环?

我正在使用-O3 进行编译。

当然,有问题的实际代码更复杂,但这里有一个具有相同行为的简化示例:

int const constants[] =  1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144 ;

int get_sum_1()

    int total = 0;
    for (int i = 0; i < CONSTANT_COUNT; ++i)
    
        total += constants[i];
    
    return total;

...如果 CONSTANT_COUNT 定义为 8(或更少),则 GCC 将展开循环,传播常量,并将整个函数简化为简单的 return &lt;value&gt;;。另一方面,如果CONSTANT_COUNT 为 9(或更大),则循环不会展开,并且 GCC 会生成一个二进制文件,该二进制文件会循环、读取常量并在运行时添加它们——尽管理论上,函数仍然可以优化到只返回一个常量。 (是的,我查看了反编译的二进制文件。)

如果我手动展开循环,像这样:

int get_sum_2()

    int total = 0;
    total += constants[0];
    total += constants[1];
    total += constants[2];
    total += constants[3];
    total += constants[4];
    total += constants[5];
    total += constants[6];
    total += constants[7];
    total += constants[8];
    //total += constants[9];
    return total;

或者这个:

#define ADD_CONSTANT(z, v, c) total += constants[v];

int get_sum_2()

    int total = 0;
    BOOST_PP_REPEAT(CONSTANT_COUNT, ADD_CONSTANT, _)
    return total;

...然后函数被优化为返回一个常量。因此,一旦展开,GCC 似乎能够处理较大循环的持续传播;挂断似乎只是让 GCC 考虑首先展开更长的循环。

但是,手动展开和 BOOST_PP_REPEAT 都不是可行的选择,因为在 某些 情况下,CONSTANT_COUNT 是运行时表达式,而 相同对于这些情况,代码仍然需要正常工作。 (在这些情况下,性能并不那么重要。)

我正在使用 C(不是 C++),因此模板元编程和 constexpr 均不可用。

我尝试过-funroll-loops-funroll-all-loops-fpeel-loops,并为max-unrolled-insnsmax-average-unrolled-insnsmax-unroll-timesmax-peeled-insnsmax-peel-timesmax-completely-peeled-insns 和 @ 设置较大的值987654340@,似乎没有任何区别。

我在 Linux 上使用 GCC 4.8.2,x86_64。

有什么想法吗?是否有我缺少的标志或参数...?

【问题讨论】:

所以,你基本上希望 GCC 弄清楚循环会计算什么,然后使用那个数字;你真的不在乎它是否在心理上展开循环,只要它把它归结为一个常数,对吧? 【参考方案1】:

我不确定此解决方法是否适用于您的实际问题,但我发现运行 Parabola GNU/Linux 的 x86_64 上的 GCC 4.9.0 20140604(预发行版)将以下循环展开到并包括 CONSTANT_COUNT == 33

int
get_sum()

  int total = 0;
  int i, j, k = 0;
  for (j = 0; j < 2; ++j)
    
      for (i = 0; i < CONSTANT_COUNT / 2; ++i)
        
          total += constants[k++];
        
    
  if (CONSTANT_COUNT % 2)
    total += constants[k];
  return total;

我只传递了-O3 标志。 get_sum 的汇编代码真的只是

movl $561, %eax
ret

我没有尝试,但也许该模式可以进一步扩展。

这对我来说似乎很奇怪,因为 - 至少在我看来 - 代码现在看起来要复杂得多。不幸的是,这是强制展开的一种相当侵入性的方式。编译器标志会更好。

【讨论】:

不幸的是,这种解决方法似乎不适用于我的真实代码,但有趣的是它适用于这里的简化代码。真正有趣的是,这(嗯,与此非常相似 - 无论如何每次循环迭代执行两次主体)让这个简单的示例一直优化到CONSTANT_COUNT==256,也许更高,但那是我试过了。【参考方案2】:

GCC 有很多关于循环展开的晦涩参数和程序参数(和optimizations)。您可以使用-funroll-loops-funroll-all-loops--param name=value,(例如 namemax-unroll-times ....)等。

gcc 的参数顺序很重要。你可能想把-O3放在前面,然后上面的奇怪选项。

但是,增加展开并不总能提高性能。

最后但同样重要的是,您可以编写自己的 GCC 插件来更改展开标准。

巧妙地使用__builtin_prefetch 可能会提高性能,请参阅this answer(但使用它粗心降低性能)

您需要进行基准测试。我的感觉是,过早的微优化会浪费你的时间。

【讨论】:

以上是关于强制/说服/欺骗 GCC 展开 _Longer_ 循环?的主要内容,如果未能解决你的问题,请参考以下文章

在 pivot_longer 之前清理标题

GCC强制静态库链接未使用的函数变量

gcc 如何在 linux 上实现 C++ 异常的堆栈展开?

强制 GCC 访问带有单词的结构

展开循环并使用矢量化进行独立求和

网络对抗技术_实验二_网络嗅探与欺骗