效能工程

受記憶體束縛與受運算束縛(屋頂線模型)

當一段程式碼太慢,有兩個根本不同的原因,而它們需要相反的修法。要嘛 CPU 真的整段時間都忙著做算術、單純就是有很多要算——那是受運算束縛(compute-bound);要嘛 CPU 多半閒坐著等資料從記憶體送來、等的時候幾乎沒做什麼真正的數學——那是受記憶體束縛(memory-bound)。知道你是哪一種,就告訴你該減少工作,還是該改善你餵機器的方式。

受記憶體束縛之所以這麼常見,是因為記憶體牆(memory wall):數十年來,CPU 的算術速度成長遠快於主記憶體速度,所以今天一次快取未命中可能要花數百個週期——久到足以做完數百次加法。一個有用的單一數字是算術強度(arithmetic intensity):每從記憶體搬一個位元組的資料所做的運算量(每位元組幾次運算)。算術強度低的程式碼(譬如把兩個大陣列相加:每載入好幾個位元組才一次加法)一生都在等記憶體,是受記憶體束縛的;強度高的程式碼(譬如稠密矩陣相乘,它把每個載入的值重複使用很多次)讓 CPU 保持忙碌,是受運算束縛的。屋頂線模型(roofline model)把這放進一張圖:以可達成效能(每秒運算數)對算術強度作圖。左邊有一道由記憶體帶寬決定的斜「屋頂」(低強度的程式碼,無論 CPU 多強都快不過帶寬允許的),右邊有一道由 CPU 尖峰運算率決定的平「屋頂」。你的核心的強度落在哪,就告訴你你被釘在哪道屋頂下——因而告訴你最佳化算術或最佳化資料搬移有沒有可能幫上忙。

它之所以重要,是因為它阻止你最佳化錯的東西:對一個受記憶體束縛的迴圈手調算術是白費力氣,因為 CPU 反正在閒——你必須改去提高算術強度(趁資料還在快取裡時重複使用、改善區域性、把迴圈切成快取大小的方塊)或減少搬動的位元組。反過來,一個受運算束縛的核心不理會快取技巧,而想要向量化、更好的演算法、或更少的運算。誠實的警告:一支真實程式可以在某個階段受記憶體束縛、另一個階段受運算束縛,屋頂線的「屋頂」是你很少達到的理想化尖峰,而知道你處於哪個區間唯一可靠的方法就是量測——PMU 的 IPC 加上快取未命中率是常見的線索(低 IPC 加很多未命中代表受記憶體束縛)。

c[i] = a[i] + b[i] -> 每搬 12 位元組約 1 次加法(低強度)-> 受記憶體束縛;較快的 RAM/較好的區域性有幫助,較快的 ALU 沒有 稠密矩陣相乘 C=A*B -> 每個值重複用約 N 次(高強度)-> 受運算束縛;向量化/更少運算有幫助,快取技巧沒有

算術強度決定區間:串流加法受記憶體束縛、稠密矩陣相乘受運算束縛——而各自都拒絕對方的最佳化。

別假設你處於哪個區間:PMU 會告訴你(低 IPC 加高快取未命中率是受記憶體束縛的經典徵兆)。屋頂線的尖峰是理想化的,而許多真實程式在不同階段間切換區間。

又稱
the memory wallroofline modelarithmetic intensitymemory-bound vs compute-bound記憶體牆屋頂線模型