陸續寫些關於新書《自己動手寫CPU》的博客,本篇主要是講解 gem5驗證數組的緩存優化
軟件優化是提高cache命中率的十分有效的手段,cache的基本原理是利用程序局部性,而軟件優化可以通過提高程序局部性,從而提高cache命中率。舉一個例子如下:
程序A:
#include <stdio.h>
int main()
{
int i=0,j=0;
long count=0;
long temp[51200][8];
for(i=0;i<51200;i++)
for(j=0;j<8;j++)
{
temp[i][j]=i*j; // 大約51200 次DCache缺失
}
}
在C語言中,二維數組在內存中是按照行的次序在按照列的次序存放,也就是說,二維數組中同一行的數據在內存中是挨着的,但同一列的數據在內存中是分散的。
假設數據cache的line size是64,long類型是8個字節,那麼在程序A中,temp[i][0]-temp[i][7]剛好是64字節,佔用數據cache的一個line,所以在寫temp[i][0]將發生一次cache缺失,在寫temp[i][1]-temp[i][7]的時候,不會發生數據缺失,所以,一共發生51200次缺失。
程序B:
#include <stdio.h>
int main()
{
int i=0,j=0;
long count=0;
long temp[51200][8];
for(i=0;i<8;i++)
for(j=0;j<51200;j++)
{
temp[j][i]=i*j; // 大約8*51200 次DCache缺失
}
}
仍然假設數據cache的line size是64,long類型是8個字節,那麼在程序B中,是先按照列的次序訪問,在按照行的次序訪問,所以每次訪問都會發生數據cache缺失,一共發生8*51200=409600次缺失。
下面我們通過gem5仿真驗證上述分析是否正確。
還是以我的開發環境爲例,gem5安裝在/root/gem5/gem5-stable-50ff05095970/路徑下,修改tests/test-progs/hello/src目錄下的hello.c,如下:
#include <stdio.h>
int main()
{
int i=0,j=0;
long count=0;
long temp[51200][8];
}
使用如下命令編譯(需要在/root/gem5/gem5-stable-50ff05095970/tests/test-progs/hello/src目錄下執行下面的命令):
gcc --static hello.c -o hello
然後使用前幾次編譯得到的X86對應的gem5.opt運行hello,並且選擇CPU模型是Timing,也就是順序流水線模型,命令如下(需要在/root/gem5/gem5-stable-50ff05095970/路徑下執行下面的命令):./build/X86/gem5.opt ./configs/example/se.py --cpu-type=timing --caches -c ./tests/test-progs/hello/src/hello
得到的統計結果在m5out/stats.txt中,找到其中的如下數據(分別是執行時間和數據cache寫操作的缺失數):
sim_seconds 0.000030 # Number of seconds simulated
......
system.cpu.dcache.WriteReq_misses::cpu.data 90
修改hello.c的代碼爲程序A,然後編譯,並使用gem5.opt運行,再次打開stat.txt得到運行時間、數據cache的缺失數如下:
sim_seconds 0.013802
......
system.cpu.dcache.WriteReq_misses::cpu.data 51290
可見數據cache的缺失數增加了51200,符合我們前期的分析。
修改hello.c的代碼爲程序B,然後編譯,並使用gem5.opt運行,再次打開stat.txt得到運行時間、數據cache的缺失數如下:
sim_seconds 0.031548
......
system.cpu.dcache.WriteReq_misses::cpu.data 409688
可見數據cache的缺失數增加了409588,接近409600,基本符合預期。通過實驗可以更加深刻的瞭解到程序優化對緩存使用的影響。
上面的結果與預期有一定誤差,可能是數組的起始地址沒有64字節對齊,也可能是因爲循環賦值導致其餘部分代碼發生變化,從而引起數據cache訪問次數的變化。