contributed by < 2020leon >
6.2.1 花了很大的力氣在講述 L1d cache 對於程式速度的影響,在實做過後也了解到,想要縮短程式的執行時間,勢必要了解資料在記憶體中的位置(空間局部性, spatial locality ),並且在寫程式的時候針對 L1d cache 的特性進行改善。
另外,在設計結構( struct
)的時候可以藉由調整成員的順序以減少空間的浪費(如避免四位元組和八位元組的資料型態交互使用)、照宣告順序存取成員、將時常存取的成員放置於結構的開頭等。在針對大型資料結構時,為了有效利用快取,可以根據結構的使用情境將時常被存取和不常被存取的成員分開成兩個結構,如此在處理這類大型結構的陣列時可以避免將不常被存取的成員放入快取中。
除了考慮空間局部性外,對齊( align )也是很重要的一環,以免從記憶體搬動資料到 cache 時缺乏效率。
在動態記憶體配置的部份,即便由 malloc
、 calloc
和 realloc
所配置的記憶體已經針對何內建型態進行對齊,但仍然可以透過 posix_memalign
自訂所要對齊的位元組數。
對於結構的部份,可以透過 GCC 的 __attribute__((aligned(n)))
進行資料的對齊。
論文列出以下幾點可盡量有效利用 L1d cache :
第一點的實現除了由工程師精簡編譯前的程式碼外,大都可藉由編譯器的最佳化( -O2
、 -O3
和 -Os
)來完成,而且都有不錯的效果。
第二點可以藉由 GCC 內建函式來實現將更為常用的指令搬到靠近 if
敘述的地方:
這類的程式碼在 Linux 核心經常出現。
第三點中,論文列出效果最好的程式對齊點:
jump
相關指令抵達的 block前兩種可藉由加入 no-op 指令使之對齊,新增的 no-op 指令並不會被執行到,對於效能的衝擊甚小。第三種情況中,由於大部分的迴圈總是接續在其他程式碼後,在迴圈前加入的 no-op 指令便會被執行,或是藉由 jump
指令直達迴圈,這樣的效能衝擊可能會對對齊所省下的成本來的高。
6.2.1 以矩陣乘法作為範例,了解到一階資料快取對於效能的影響。
首先是直觀的實做。
所花費的時間約為 3.19 秒。但上述方法可以很明顯看到 b
矩陣是 cache unfriendly 的,原因是沒有有效利用到 cache 的空間局部性。因此嘗試將矩陣轉置過後再相乘。
所花費的時間約為 0.93 秒,約為第一個實做的三分之一。在上方的函式中,將乘號後方的矩陣 b
轉置為 d
後,在做乘法時,由於所取得的元素均在相鄰的位置上,所以發生 cache miss 的機率遠小於第一個實做。
最後是考慮 L1d cache 的矩陣乘法。在此 cahce line size 為 64 。
L1d cache line size 可由以下命令取得:
所花費的時間約為 0.31 秒。這次更是使還在 L1d cache 中的資料在計算完關於它的部份後才換下一個 cache line ,使得 L1d cache 發揮它最大的效益。
Origin | Transposed | Sub Matrix |
---|---|---|
3194333615 | 931789102 | 307312426 |
100.0000% | 29.1701% | 9.6205% |
在閱讀了《每位程式開發者都該有的記憶體知識》譯文後,除了發現內部有些尚未翻譯的原文語句外,在查閱過去的 commits 後,亦發現部份尚未完全解決的問題,因此著手修改譯文。以下列出所觀察到的問題:
在經過修正後,已經對原 repo 發出 PR。