前兩天碼哥寫了一篇《你應該知道的C語言Cache命中率提升法》的文章,講述關於地址連續性帶來的cache命中率提升,感興趣的朋友可以先翻看一番。
今天的文章是關於如何優化結構體成員來提升cache命中率的。我們先來看一個例子:
代碼一
<code>/* a.c */#include <stdio.h>#includetypedef struct test_s { long i0; char padding0[1024]; long i1; char padding1[1024]; long i2; char padding2[1024]; long i3; char padding3[1024]; long i4; char padding4[1024]; long i5; char padding5[1024]; long i6; char padding6[1024]; long i7; char padding7[1024]; long i8; char padding9[1024]; long i9;} test_t;int main(void){ test_t arr[512]; int i; struct timeval begin, end; gettimeofday(&begin, NULL); for (i = 0; i < sizeof(arr)/sizeof(test_t); ++i) { arr[i].i0 = 0; arr[i].i1 = 1; arr[i].i2 = 2; arr[i].i3 = 3; arr[i].i4 = 4; arr[i].i5 = 5; arr[i].i6 = 6; arr[i].i7 = 7; arr[i].i8 = 8; arr[i].i9 = 9; } gettimeofday(&end, NULL); printf("%lu(us)\\n", (end.tv_sec*1000000+end.tv_usec)-(begin.tv_sec*1000000+begin.tv_usec)); return 0;} /<stdio.h>/<code>
功能很簡單,我們定義了一個結構體,其中有很多padding,這些padding是用來模擬日常項目中不常訪問的結構體成員。然後我們定義了這樣一個結構體數組,順序訪問每個結構體,並將其中的整型成員進行賦值,並度量這一循環的時間開銷。
在碼哥的測試機上,執行的結果大約是:
<code>$ ./a2487(us)/<code>
參考我們之前的那篇關於地址連續性帶來cache命中率提升想法,上面這個例子是否有性能提升的空間呢?
答案當然是有的。請看下面的代碼:
代碼二
<code>/* b.c */#include <stdio.h>#includetypedef struct test_s { long i0; long i1; long i2; long i3; long i4; long i5; long i6; long i7; long i8; long i9; char padding0[1024]; char padding1[1024]; char padding2[1024]; char padding3[1024]; char padding4[1024]; char padding5[1024]; char padding6[1024]; char padding7[1024]; char padding8[1024];} test_t;int main(void){ test_t arr[512]; int i; struct timeval begin, end; gettimeofday(&begin, NULL); for (i = 0; i < sizeof(arr)/sizeof(test_t); ++i) { arr[i].i0 = 0; arr[i].i1 = 1; arr[i].i2 = 2; arr[i].i3 = 3; arr[i].i4 = 4; arr[i].i5 = 5; arr[i].i6 = 6; arr[i].i7 = 7; arr[i].i8 = 8; arr[i].i9 = 9; } gettimeofday(&end, NULL); printf("%lu(us)\\n", (end.tv_sec*1000000+end.tv_usec)-(begin.tv_sec*1000000+begin.tv_usec)); return 0;} /<stdio.h>/<code>
可以看到,這段代碼中出了結構體中成員的位置有所調整外,其餘代碼都是一致的,甚至結構體的大小都是一樣的。
那麼這段代碼的執行時間又是如何的呢?
<code>$ ./b1034(us)/<code>
可以看到這個結果比代碼一快了1倍左右。
總結
為何會快出1倍,原因與地址連續性依舊有關。代碼二中,常被訪問的10個整型成員被安排在了一起,這樣當訪問其中一個時,可以儘可能多的將可能被訪問的成員預加載到cache中。而代碼一中,由於間隔了很多padding,且每個padding也比較大,因此cache緩存了很多不常被訪問的部分,所以在我們給每一個整型賦值時都無法利用到前一次賦值的cache緩存,因此效率有所降低。
結論很簡單,
儘可能將常訪問的結構體成員放在一起,甚至推薦貼近結構體開始處存放。碼哥在這裡給大家拜年啦,祝大家在新的一年裡,身體健康,家庭美滿,事業有成,走上巔峰。
另外,最近疫情也比較嚴峻,大家出門不要忘記做好必要的防護措施。
閱讀更多 碼哥比特 的文章