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

我如何说服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 ; 。 另一方面,如果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-insns设置大值max-peeled-insnsmax-peel-timesmax-completely-peeled-insnsmax-completely-peel-times ,这些都没有什么区别。

我在Linux,x86_64上使用GCC 4.8.2。

有任何想法吗? 有没有我缺少的旗帜或参数……?

我不确定这种解决方法是否适用于您的实际问题,但我发现运行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 

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

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

GCC有很多关于循环展开(和优化 )的模糊参数和程序参数。 您可以使用-funroll-loops-funroll-all-loops , – --param name = value ,(例如, 名称max-unroll-times ….)等。

gcc的参数顺序很重要。 你可能想先把-O3和上面的奇怪选项放在它之后。

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

最后但并非最不重要的是,您可以编写自己的GCC插件,这将改变展开标准。

巧妙地使用__builtin_prefetch可能会提高性能,请参阅此答案 (但不小心使用会降低性能)

你需要进行基准测试。 我的感觉是,过早的微观优化是你的时间的巨大损失。