7個示例科普CPU Cache

2021-07-11 07:19:55 字數 2985 閱讀 8323

cpu cache一直是理解計算機體系架構的重要知識點,也是併發程式設計設計中的技術難點,而且相關參考資料如同過江之鯽,浩瀚繁星,閱之如臨深淵,味同嚼蠟,三言兩語難以入門。正好網上有人推薦了微軟大牛igor ostrovsky一篇博文《漫遊處理器快取效應》,文章不僅僅用7個最簡單的原始碼示例就將cpu cache的原理娓娓道來,還附加圖表量化分析做數學上的佐證,個人感覺這種案例教學的切入方式絕對是俺的菜,故而忍不住貿然譯之,以饗列位看官。

大多數讀者都知道cache是一種快速小型的記憶體,用以儲存最近訪問記憶體位置。這種描述合理而準確,但是更多地了解一些處理器快取工作中的「煩人」細節對於理解程式執行效能有很大幫助。

在這篇部落格中,我將運用**示例來詳解cache工作的方方面面,以及對現實世界中程式執行產生的影響。

下面的例子都是用c#寫的,但語言的選擇同程式執行狀況以及得出的結論幾乎沒什麼影響。

示例1:記憶體訪問和執行

你認為相較於迴圈1,迴圈2會執行多快?

1 2

3 4

5 6

7 int arr = new int[64 * 1024 * 1024];

// loop 1

for (int i = 0; i < arr.length; i++) arr[i] *= 3;

// loop 2

for (int i = 0; i < arr.length; i += 16) arr[i] *= 3;

兩個迴圈花費相同時間的原因跟記憶體有關。迴圈執行時間長短由陣列的記憶體訪問次數決定的,而非整型數的乘法運算次數。經過下面對第二個示例的解釋,你會發現硬體對這兩個迴圈的主存訪問次數是相同的。

示例2:快取行的影響

讓我們進一步探索這個例子。我們將嘗試不同的迴圈步長,而不僅僅是1和16。

running times of this loop for different step values (k)

注意當步長在1到16範圍內,迴圈執行時間幾乎不變。但從16開始,每次步長加倍,執行時間減半。

背後的原因是今天的cpu不再是按位元組訪問記憶體,而是以64位元組為單位的塊(chunk)拿取,稱為乙個快取行(cache line)。當你讀乙個特定的記憶體位址,整個快取行將從主存換入快取,並且訪問同乙個快取行內的其它值的開銷是很小的。

由於16個整型數占用64位元組(乙個快取行),for迴圈步長在1到16之間必定接觸到相同數目的快取行:即陣列中所有的快取行。當步長為32,我們只有大約每兩個快取行接觸一次,當步長為64,只有每四個接觸一次。

理解快取行對某些型別的程式優化而言可能很重要。比如,資料位元組對齊可能決定一次操作接觸1個還是2個快取行。那上面的例子來說,很顯然操作不對齊的資料將損失一半效能。

示例3:l1和l2快取大小

今天的計算機具有兩級或**快取,通常叫做l1、l2以及可能的l3(譯者注:如果你不明白什麼叫二級快取,可以參考這篇精悍的博文lol)。如果你想知道不同快取的大小,你可以使用系統內部工具coreinfo,或者windows api呼叫getlogicalprocessorinfo。兩者都將告訴你快取行以及快取本身的大小。

在我的機器上,coreinfo現實我有乙個32kb的l1資料快取,乙個32kb的l1指令快取,還有乙個4mb大小l2資料快取。l1快取是處理器獨享的,l2快取是成對處理器共享的。

logical processor to cache map:

*— data cache 0, level 1, 32 kb, assoc 8, linesize 64

*— instruction cache 0, level 1, 32 kb, assoc 8, linesize 64

-*– data cache 1, level 1, 32 kb, assoc 8, linesize 64

-*– instruction cache 1, level 1, 32 kb, assoc 8, linesize 64

**– unified cache 0, level 2, 4 mb, assoc 16, linesize 64

–*- data cache 2, level 1, 32 kb, assoc 8, linesize 64

–*- instruction cache 2, level 1, 32 kb, assoc 8, linesize 64

—* data cache 3, level 1, 32 kb, assoc 8, linesize 64

—* instruction cache 3, level 1, 32 kb, assoc 8, linesize 64

–** unified cache 1, level 2, 4 mb, assoc 16, linesize 64

(譯者注:作者平台是四核機,所以l1編號為0~3,資料/指令各乙個,l2只有資料快取,兩個處理器共享乙個,編號0~1。關聯性欄位在後面例子說明。)

讓我們通過乙個實驗來驗證這些數字。遍歷乙個整型陣列,每16個值自增1——一種節約地方式改變每個快取行。當遍歷到最後乙個值,就重頭開始。我們將使用不同的陣列大小,可以看到當陣列溢位一級快取大小,程式執行的效能將急劇滑落。

1 2

3 4

5 6

7 int steps = 64 * 1024 * 1024;

// arbitrary number of steps

int lengthmod = arr.length - 1;

for (int i = 0; i < steps; i++)

下圖是執行時間圖表:

cache size

你可以看到在32kb和4mb之後效能明顯滑落——正好是我機器上l1和l2快取大小。

示例4:指令級別併發

現在讓我們看一看不同的東西。下面兩個迴圈中你以為哪個較快?

1 2

3 4

5 6

7 8

int steps = 256 * 1024 * 1024;

int a = new int[2];

// loop 1

for (int i=0; i

TS科普7 助記符

bslbf 位元串,左位元為首,其中 左 為順序,按該順序位元串寫入本建議書 國際標準中。位元串被寫成在單引號標誌內的1和0的字串,例如 1000 0001 位元串內的空位是為了閱讀方便而無任何意義。ch 通道 gr 音訊層ii中3x32子帶樣本 音訊層iii中18x32子帶樣本的區位。main d...

乙個PlaySound示例

using system using system.collections using system.componentmodel using system.runtime.interopservices internal class helpers dllimport winmm public s...

6個Expect指令碼示例

本文譯至 expect 指令碼語言用於自動提交輸入到互動程式。它相比其它指令碼語言簡單易學。使用expect指令碼的系統管理員和開發人員可以輕鬆地自動化冗餘任務。它的工作原理是等待特定字串,並傳送或響應相應的字串。以下三個expect命令用於任何自動化互動的過程。請確保在您的系統上安裝expect軟...