循環通常是數據並行應用的最密集計算段,因此循環優化直接影響性能。當面對嵌套循環時,分配給線程的計算粒度將直接影響性能。循環轉換如Fission(循環裂變)與Fusion(循環融合)的嵌套式循環可促使並行化,並提高工作效率。
本文就幾種循環優化的方法與多面體模型的調度進行簡要闡述(因爲LZ已經被這些循環優化搞得痛不欲生了)。
Loop fusion
顧名思義,該變換令循環進行了融合。如圖所示,原始代碼是兩個for循環,變化之後,對兩個循環中 i、j 相同的範圍進行了融合。
我們爲了把該變換映射或者說調度到多面體模型中去,需要產生調度樹。
舉例如下:
有這樣一段用TC寫的代碼,語句S、T分別爲循環語句。其融合操作表述如下:
Band 操作將S、T所對應的迭代向量進行了融合。
Loop tiling
Loop tiling/blocking 的意思是分塊,可以將循環的迭代空間劃分爲更小的塊,以幫助確保循環中使用的數據在重用之前一直保存在緩存中。循環迭代空間的劃分導致將大數組劃分爲更小的塊,從而將被訪問的數組元素匹配到緩存大小中,增強緩存重用,消除緩存大小需求。
平常的循環:
for(i=0; i<N; ++i){
...
}
變換後的循環,擁有一個全新的 block 大小B:
for(j=0; j<N; j+=B){
for(i=j; i<min(N, j+B); ++i){
....
}
}
更具體的例子:
下面是一個矩陣向量乘法的例子。有三個數組,每個數組有100個元素。代碼沒有將數組劃分爲更小的大小。
int i, j, a[100][100], b[100], c[100];
int n = 100;
for (i = 0; i < n; i++) {
c[i] = 0;
for (j = 0; j < n; j++) {
c[i] = c[i] + a[i][j] * b[j];
}
}
當我們應用循環分塊,使用2 * 2塊,代碼變爲:
int i, j, x, y, a[100][100], b[100], c[100];
int n = 100;
for (i = 0; i < n; i += 2) {
c[i] = 0;
c[i + 1] = 0;
for (j = 0; j < n; j += 2) {
for (x = i; x < min(i + 2, n); x++) {
for (y = j; y < min(j + 2, n); y++) {
c[x] = c[x] + a[x][y] * b[y];
}
}
}
}
原來的循環迭代空間n×n。數組的訪問塊(i, j)也是n×n。當n過大和機器的緩存容量太小,訪問數組元素的循環迭代(例如,i = 1, j = 1到n)可能交叉的高速緩存線路,導致緩存丟失。