迴圈一直令我們頭疼,因為迴圈體內總是隱藏著熱點!看一段示例**:
for(i = 0; i < 10; i++)
觀察迴圈體彙編**,我們很容易發現,由於迴圈體的內容相對簡單,以至於這個迴圈實際執行過程中差不多一半的指令都在為檢查迴圈執行的條件而服務。如果計算迴圈索引和測試迴圈條件的迴圈開銷部分所佔比重過大,這時就可以考慮使用一種被稱作"迴圈展開"的方式來優化**。所謂迴圈展開就是通過在每次迭代中執行更多的資料操作來減小迴圈開銷的影響。其基本思想是設法把操作物件線性化,並且在一次迭代中訪問線性資料中的一小組而非單獨的某個。這樣得到的程式將執行更少的迭代次數,於是迴圈開銷就被有效地降低了。
如果這時還是希望能夠對迴圈進行3次展開,那麼該如何處理呢。可以從兩個方面來解決這個需求。首先,要確保第一次迴圈不會超過陣列的界限。這並不難做到,對於長度為n的陣列,可以將迴圈限制設為n-2。然後,保證只有當迴圈索引i
#include
voidfunction(intarray,int*dest)
for(; i < 10; i++)
*dest = temp;
}
voidmain()
;
intnumber = 1;
int* dest = &number;
function(array, dest);
printf( "%d/n"
, *dest);
}
將這種方法推廣到更加通用的層面上,如果迴圈展開k次,就可以把上限設為n-k+1,那麼最大迴圈索引i+k-1將會比n小。然後,再加上第二個迴圈,以每次處理乙個元素的方式處理陣列的最後幾個元素。
迴圈展開技術的好處在於它能減小迴圈開銷的影響。但它也不是沒有缺點的,天下沒有免費的午餐!首先,迴圈展開增加了生成的目標**的數量,這很容易理解,因為迴圈體在源**級別就已經變得龐大。讀者可以試想它們被翻譯成目標**時的情況。為了驗證這一點,讀者可以使用visual c++來對比使用迴圈展開前後迴圈體的彙編**的長度,驗證結果將表明迴圈展開對目標**的長度的確有很大的影響。當然,在我們所舉的例子中,迴圈展開所要付出的代價都是比較小的。當然這並不能概括其他所有的情況,因此這個空間換時間的折中最優位置還需要針對具體問題來做具體的分析。
使用迴圈展開時一方面要考慮實際待處理陣列的長度,並由此選擇乙個較好的展開度;另一方面要綜合考慮這個展開度對時空開銷比例的影響,在盡量不會使目標**空間消耗激增的前提下獲得最高的時間收益。另外,也可以讓編譯器為我們完成這些工作。通常,編譯器可以很容易地執行迴圈展開,但這需要設定其優化級別足夠高,所以程式設計師也可以選擇讓編譯器來完成這個工作。當然,我們曾經提醒過讀者,在開發階段並不適合將優化級別設定得過高,因此如果你希望讓編譯器執行迴圈展開,那麼最好等到軟體開發完成之後。
Loop Unrolling 迴圈展開
以書中計算陣列和的兩段 為例 1.未展開 void psum1 float a,float p,long int n 2.展開 void psum2 float a,float p,long int n for odd n,finish remaining element if i n p i p ...
k路並行暫存器溢位 迴圈展開
1.當三次迴圈展開 三個並行變數的時候 有三條關鍵路徑,每條路徑之間無資料相關,可以並行執行。看上去關鍵路徑上只有乙個add,整數的add延遲為1。但是我們可以仔細分析流水線上各個操作的發射時間和延遲時間,來確定每乙個操作開始的時間點。如下表 迴圈次數 add開始時間 mull開始時間 load1開...
CUDA效能調優(一) 合併訪問 迴圈展開
1 合併訪問 當同乙個warp中的所有執行緒都執行同一條指令訪問全域性儲存器中連續的單元時,就獲得最有利的訪問模式。硬體檢測到同乙個warp中的這些執行緒訪問全域性儲存器中連續的儲存單元,並將這些單元結合成乙個合併的訪問。合併訪問可以提高dram的頻寬利用率,使dram在傳輸資料時的速度接近全域性儲...