在为布尔数组编写“不等扫描”的过程中, 我最后写了这个循环:
// Heckman recursive doubling
#ifdef STRENGTHREDUCTION // Haswell/gcc does not like the multiply
for( s=1; s<BITSINWORD; s=s*2) {
#else // STRENGTHREDUCTION
for( s=1; s<BITSINWORD; s=s+s) {
#endif // STRENGTHREDUCTION
w = w XOR ( w >> s);
}
我观察到的是gcc WOULD展开s = s * 2循环, 但不是s = s + s循环。这有点不直观,如 对于添加的循环计数分析应该,IMO更简单 而不是乘法。我怀疑gcc知道s = s + s 循环计数,只是腼腆。
有没有人知道这是否有充分理由 gcc的行为? 我出于好奇而问这个......
[展开的版本,BTW,比循环慢了一点。]
谢谢, 罗伯特
答案 0 :(得分:0)
这很有趣。
首先猜测
我的第一个猜测是gcc的循环展开分析预计添加案例会从循环展开中受益更少,因为interMarket
增长得更慢。
我试验了以下代码:
s
除了循环之外,另一个版本是#include <stdio.h>
int main(int argc, char **args) {
int s;
int w = 255;
for (s = 1; s < 32; s = s * 2)
{
w = w ^ (w >> s);
}
printf("%d", w); // To prevent everything from being optimized away
return 0;
}
。我发现gcc 4.9.2在乘法版本中展开循环而不是添加循环。这是用
s = s + s
所以我的第一个猜测是gcc假设添加版本,如果展开,将导致更多字节的代码适合icache,因此不优化。但是,在添加版本中将循环条件从gcc -S -O3 test.c
更改为s < 32
仍然不会导致优化,即使看起来gcc应该很容易识别循环的迭代非常少
我的下一次尝试(返回s < 4
作为条件)是明确告诉gcc最多展开循环100次:
s < 32
这仍然会在装配中产生一个循环。尝试使用--param max-unrolled-insns在展开的循环中允许更多指令也保留循环。因此,我们几乎可以消除gcc认为展开效率低下的可能性。
有趣的是,尝试使用gcc -S -O3 -fverbose-asm --param max-unroll-times=100 test.c
处的clang进行编译会立即展开循环。 clang是known to unroll more aggressively,但这似乎不是一个令人满意的答案。
我可以让gcc通过添加一个常量而不是-O3
本身来展开添加循环,也就是说,我做s
。然后循环展开。
第二次猜测
这导致我理论上,如果循环的增加值多次取决于计数器的值,那么gcc无法理解循环将运行多少次迭代(展开所必需的)。我按如下方式更改循环:
s = s + 2
它没有展开gcc,而clang展开它。所以我最好的猜测是,当循环的增量语句的格式为for (s = 2; s < 32; s = s*s)
时,gcc无法计算迭代次数。
答案 1 :(得分:0)
编译器通常会执行强度降低,所以我希望如此 gcc会在这里使用它,用s + s替换s * 2,此时两者的形式 源代码表达式将匹配。
如果不是这样,那么我认为这是gcc中的一个错误。分析 使用s + s计算循环计数(略微)比这简单 使用s * 2,所以我希望gcc会(略) 更有可能展开s + s案例。