工作集與區域性
程式不會隨機地碰記憶體。在任何一小段時間裡,程式總是一再回到同一小撮頁——它正在跑的迴圈、它正在咀嚼的資料——而把其餘的晾在一旁。工作集(working set)就是程式此刻正積極使用的那組頁;參考區域性(locality of reference)則是讓工作集既小又可預測的那種普遍傾向。
區域性有兩種風味,都值得記住。時間區域性(temporal locality):若你最近用過某個位址,你很可能很快又用到它(迴圈計數器、頻繁呼叫的函式)。空間區域性(spatial locality):若你用過某個位址,你很可能很快用到鄰近的(一個元素一個元素地走過陣列、同一結構的各欄位)。兩者合起來意味著任何時刻只有不多的頁是「熱」的,而一個區域性良好的程式會把那組熱頁維持得很緊。工作集本質上就是它在不久的過去碰過的頁,用來猜它接下來會碰什麼。
這正是讓整個記憶體階層運作的概念。TLB、CPU 快取、需求分頁與分頁逐出策略全都賭區域性:把工作集留在快的記憶體裡,讓冷掉的其餘待在慢的儲存裝置。只要一支程式的工作集裝得進 RAM,分頁就便宜而罕見。危險出現在工作集長得超過 RAM 時——那正是輾轉的條件。寫對記憶體友善的程式碼(依序走訪陣列、把相關資料放在一起),主要就是在縮小並收緊工作集。
依序加總一個百萬元素的陣列,會一條一條、一頁一頁地依序各碰一次——工作集很小、區域性很好。改用隨機索引跳著加總,會碰到四處散落的頁,把工作集撐大,對同樣的算術造成多得多的 TLB 與快取未命中。
同樣的工作、不同的區域性——速度卻大不相同。
區域性是強烈的傾向,不是保證——追逐指標的資料結構與隨機存取樣式會刻意打破它。當執行中程式合起來的工作集超過 RAM 時,你會得到輾轉;把工作集維持得小,是良好記憶體效能的實用槓桿。