2016-11-04 45 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情況。