2016-11-04 109 views
1

在写“不等于扫描”布尔阵列的过程中, 我结束了写这个循环:GCC循环展开古怪

// 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知道循环次数,并且仅仅是co。。

有没有人知道这个 行为对gcc的部分是否有一些很好的理由? 我问这是出于好奇...

[展开版本,顺便说一句,跑得比循环慢公平一点。]

感谢, 罗伯特

回答

0

这很有趣。

首先猜测

我的第一个猜想是,GCC的循环展开分析预计,除了案件从循环展开少受益,因为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; 
} 

而另一个版本是一样的,除了环路具有s = s + s。我发现gcc 4.9.2展开了乘法版本中的循环,但不是加法版本。这与

gcc -S -O3 test.c 

编译所以我的第一个猜测是,GCC假设添加剂版本,如果展开,将导致适合于将指令,因此不优化更多的字节代码。但是,在加法版本中将环路条件从s < 32更改为s < 4仍然不会导致优化,即使看起来gcc应该轻易认识到该循环的迭代次数非常少。

我的下一次尝试(去回到s < 32为条件)是明确地告诉GCC最多解开循环100次:

gcc -S -O3 -fverbose-asm --param max-unroll-times=100 test.c 

这仍然会产生在装配一环。尝试在展开循环中允许使用--param max-unrolled-insns中的更多指令来保留循环。因此,我们几乎可以消除gcc认为展开效率低下的可能性。

有趣的是,试图在-O3处与clang一起编译立即展开循环。铛是known to unroll more aggressively,但这似乎不是一个令人满意的答案。

我可以得到gcc展开添加剂循环通过使其添加一个常数,而不是s本身,也就是我做s = s + 2。然后循环展开。

第二次猜测

这导致我的理论认为GCC是无法了解有多少次迭代循环将用于运行(必要展开)如果循环的增加值取决于计数器的值超过一次。我改变循环如下:

for (s = 2; s < 32; s = s*s) 

而且它不会与gcc展开,而铿锵展开它。所以我最好的猜测是,当循环的增量语句形式为s = s (op) s时,gcc无法计算迭代次数。

0

编译器通常会执行强度降低,因此我期望gcc会在这里使用它,用s + s替换s * 2,此时源代码表达式的形式将匹配。

如果情况并非如此,那么我认为这是gcc中的一个bug。使用s + s计算循环计数的分析 与使用s * 2的 (稍微)相比更简单,因此我认为gcc将会(稍微) 更有可能展开s + s情况。