循環展開(#pragma unroll)
1)什麼是循環展開?
循環展開顧名思義就是將循環體展開,全部展開或者展開一部分都可以有效提高性能。
循環展開無論是在CPU還是GPU上,都可以有效的提高應用程序運行速度。
以下是一個循環體
float sum=0;
for(int i=0;i<n;++i)
{
sum+=a[i];
}
循環部分展開
for(int i=0;i<n;i+=2)
{
sum+=a[i]+a[i+1];
}
2)爲什麼要循環展開
我們知道執行核函數時,通常以warp爲單位去執行指令的。當warp去執行循環時( 線程ID去做for的判斷條件 或者 for裏有線程ID的if條件 ),會產生分支衝突,增加指令數。
所以循環展開可以有效避免分支衝突,提高性能。
3)循環展開在GPU中的應用
編譯器會默認展開帶有循環計數的小循環(比如上述例子中的N是常數的話)。而#pragma unroll 指令則可用於控制任何給定循環的展開。它必須放置在循環前,並只應用於此循環。它後面可以跟一個數字,用於指定循環必須展開多少次。
下列代碼示例中循環將展開5次
#pragma unroll 5
for(int i=0;i<n;++i)
此時,要確保n>=5,不然會影響程序運算結果。
注意(當循環展開之後需要用到寄存器時):
循環展開會使用更多的寄存器,編譯器在編譯的過程中會將確定的量優先存儲在寄存器中,這就導致有些變量會被存儲到局部內存。(循環展開會消耗更多的寄存器,而不展開是不會的)。SM裏的寄存器大小是有限的,SM會根據一個塊需要消耗的寄存器大小和線程的個數去分配該SM上塊的個數,當一個SM連一個塊都分配不了時,就會導致內核啓動不了。
此時的解決辦法就只有 減少線程的數量去換取更多的寄存器。
所以循環的展開應該在寄存器適用的範圍去展開,不能過度展開。如何保證不過度展開?就是權衡寄存器大小和線程數量之間的關係。