幾種通過降低Cache失效率來提升程式效能的方法
幾種通過降低Cache失效率來提升程式效能的方法
當程式訪問多個數組時,經常會出現有些陣列按行訪問,有些陣列按列訪問的情況。以矩陣的乘法為例, C = A × B C=A\times B C=A×B ,經典的計算矩陣乘法的演算法如下:
void mult() { for(int i=0;i<N;i++) { for(int j=0;j<N;j++) { double res=0.0; for(int k=0;k<N;k++) { res += (matrix_a[i][k]*matrix_b[k][j]); } matrix_c[i][j] = res; } } }
可以看出,內部的兩個迴圈
j
j
j ,
k
k
k, 將反覆讀取矩陣
m
a
t
r
i
x
_
b
[
N
]
[
N
]
matrix\_b[N][N]
matrix_b[N][N] 的全部的
N
×
N
N\times N
N×N 個元素,以及反覆讀取矩陣
m
a
t
r
i
x
_
a
[
N
]
[
N
]
matrix\_a[N][N]
matrix_a[N][N] 的全部的第
i
i
i 行的
N
N
N 個元素,所產生的結果
r
e
s
res
res 將存入至結果矩陣
m
a
t
r
i
x
_
c
[
N
]
[
N
]
matrix\_c[N][N]
考慮此時 Cache 的失效情況,拋開 Cache 大小無限制或者 Cache 的大小完全能裝下這三個陣列的最為理想的情況。考慮對三個陣列訪問時,導致 Cache 失效的情況,最壞的情況是,每次的訪問都失效,這是會導致共 2 N 3 + N 2 2N^3 + N^2 2N3+N2 次的失效。為了減少失效,下面有兩種方法能顯著的減小 Cache 的失效率。
1 將矩陣轉置
void mult() { int i,j,k; for(i=0;i<N;i++) { for(j=0;j<N;j++) matrix_c[j][i]=matrix_b[i][j]; } for(int i=0;i<N;i++) { for(int j=0;j<N;j++) { double res=0.0; for(int k=0;k<N;k++) { res += (matrix_a[i][k]*matrix_c[j][k]); } matrix_c[i][j] = res; } } }
毫無疑問的是,轉置矩陣也是一個耗時的過程,但在轉置處花費的時間和後面計算矩陣的乘積結果所節省的時間達到了很好的折中,下面是測試圖:
(矩陣規模是 1025 × 1025 1025\times 1025 1025×1025,生成矩陣的方法是生成隨機的 double 型別的浮點數 )
可以看到,轉置操作再加上轉置後的矩陣乘的開銷遠遠小於直接矩陣乘的開銷。
其中最主要的原因就是,轉置後的矩陣乘法,最內部的迴圈 k k k ,都將反覆讀取矩陣 m a t r i x _ a [ N ] [ N ] matrix\_a[N][N] matrix_a[N][N] 的全部的第 i i i 行的 N N N 個元素,以及矩陣 m a t r i x _ b [ N ] [ N ] matrix\_b[N][N] matrix_b[N][N] 的全部的第 j j j 行的 N N N 個元素,而這樣的讀取方式,很好地利用了空間區域性性,讓對於程式設計師透明的 Cache 發揮了重要的作用。
2 分塊處理矩陣乘法
分塊是一種經典的利用 Cache 來提升程式效能的技術。分塊演算法不是對陣列的整行或者整列進行訪問的,而是把對大陣列的訪問分解成對子矩陣的訪問。
為了保證正在訪問的元素能在 Cache 中命中,把原程式內部改為僅僅對大小為 B × B B\times B B×B 的子陣列進行計算,其中 B B B 稱為分塊因子,程式碼如下:
void multB() {
int jj,kk,i,j,k;
double r;
for(jj=0;jj<N;jj+=B)
for(kk=0;kk<N;kk+=B)
for(i=0;i<N;i++) //處理 BxB 大小的子矩陣
for(j=jj;j<Min((jj+B),(N));j++){
r=0.0;
for(k=kk;k<Min((kk+B),(N));k++)
r+=(matrix_a[i][k]*matrix_c[k][j]);
matrix_c[i][j]+=r;
}
}
這時再以開始的條件考慮失效次數,可以初步判斷 Cache 的失效率會降低,測試結果如下:
3 將分塊和轉置結合
程式碼如下:
void mult_T_and_B() {
int jj,kk,i,j,k;
double r;
for(i=0;i<N;i++) {
for(j=0;j<N;j++)
matrix_c[j][i]=matrix_b[i][j];
}
for(jj=0;jj<N;jj+=B)
for(kk=0;kk<N;kk+=B)
for(i=0;i<N;i++)
for(j=jj;j<Min((jj+B),(N));j++){
r=0.0;
for(k=kk;k<Min((kk+B),(N));k++)
r+=(matrix_a[i][k]*matrix_c[j][k]);
matrix_c[i][j]+=r;
}
}
測試時間如下:
4 總結
程式的平均訪存公式如下:
平
均
訪
存
時
間
=
命
中
時
間
+
失
效
率
×
失
效
開
銷
平均訪存時間=命中時間+失效率\times 失效開銷
平均訪存時間=命中時間+失效率×失效開銷
-
以上討論的提升程式效能的方法的根本就是在降低失效率。
-
雖然Cache 對程式設計師而言是透明的,但深入理解 Cache 的基本原理,能在不經意間提高程式的效能。