1. 前言
最近閱讀了一些UNIX系統書籍,對於多處理器系統和高速緩存的機制有了更多的理解,寫下本文備忘。
2. 多處理器的高速緩存架構
2.1 SMP
最簡單的多處理器架構是一種被稱爲對稱多處理器結構(SMP:Symmetric Multi-Processor)的架構,SMP的邏輯架構如下圖:
在上圖中,我們可以清楚的看出SMP架構的特點:
- 緊密耦合,所有的CPU、存儲器和IO都是緊密耦合的,使用一條公共高速總線把所有的單元互連起來。
- 共享內存,所有的CPU共用單獨一個全局能夠訪問到存儲模塊,CPU本身沒有局部存儲器(不是高速緩存),它們把程序指令和數據都保存在全局共享存儲器(global shared memory)中。一個CPU在存儲器中保存的數據,對於其他CPU是可見的。
- 對稱,存儲器的訪問是對稱的,所有CPU訪問存儲器的時間是相同的,內容是完全共享的。
同時,我們也可以看出,SMP架構支持的最大CPU核數受到共享總線和存儲器帶寬的限制。
2.2 帶高速緩存的SMP架構
高速緩存是一種高速的存儲系統,它保存有主存儲器很小的一個子集,它位置根據層次的不同可以在CPU內部,也可以位於主板上。
高速緩存的邏輯架構如下圖:
高速緩存通過引入局部性來改善系統性能,有兩種類型的局部性:
時間局部性:程序有可能重複使用近期引用過的變量;
空間局部性:程序有可能重複使用前面附近引用過的變量。
帶高速緩存的SMP邏輯架構如下圖:
在這種邏輯架構中,每個CPU都有自己私有的高速緩存。當然在實際生產中,存在L1 L2 L3等多層次緩存的區別,有可能多個核共享高速緩存。
3. 多種硬件高速緩存一致性協議
由於不能讓進程訪問到過期數據,高速緩存中的數據必須和主存保持一致。在MP系統中,多個CPU有可能同時訪問主存上的某條數據,因此需要通過軟件在上下文切換時沖洗高速緩存或者利用具有總線監視功能的硬件來保證緩存一致性。
相對而已,硬件緩存一致性協議(cache consistency protocol)無需軟件介入,就可以保持處理器之間共享數據的一致性,對於操作系統內核是透明的,是目前常用的方案。
3.1 寫直通-使無效協議
當CPU對一個緩存行進行寫操作時,除了執行寫操作的CPU上副本外,其他高速緩存的副本全部失效。最簡單的寫-使無效協議就是寫直通-使無效協議。MESI和MESIF也屬於寫-使無效協議。
寫直通-使無效協議策略是,當一個處理器寫共享數據時,它會立即被寫入主存。同時執行此次寫操作的總線事務被系統內其他高速緩存監聽,而且如果寫入的數據在這些高速緩存中命中的話,它們會讓自己的緩存行副本無效。這迫使與他們自己關聯的CPU在下次訪問該行時,必須從主存中讀取該行的新內容。
寫直通-使無效協議修改數據的時序圖如下:
寫直通-使無效協議的缺點也是非常明顯的,對於任何共享數據的寫操作都需要一次總線事務,效率非常低下。
3.2 MESI
MESI協議給緩存行引入了所有權的概念。一個緩存行有下列4種狀態,協議名正是4種狀態的首字母:
Modified 已修改:緩存行相對於主存已經被修改,其他高速緩存沒有該行的副本。在此狀態下,當前CPU可以繼續修改該行而不觸發總線事務。
Exclusive 獨佔:緩存行與主存一致,且其他高速緩存沒有該行的副本;寫該行時,狀態轉變爲已修改,且不觸發總線事務。
Share 共享:緩存行與主存一致,且其他高速緩存存在該行副本;修改該行之前,必須觸發一次總線事務,使該行其他副本無效。
Invalid 無效:緩存行已失效,等待沖洗。
MESI狀態轉換如下圖所示:
MESI在發生緩存缺失時,會發起一次總線事務讀取,讀取主存數據。當返回數據時,MESI協議提供了一個特殊的總線信號,指出其他高速緩存中是否存在該行副本。如有,緩存行狀態爲Share;如無,緩存行狀態爲Exclusive。
3.3 MESIF
MESIF是對MESI的優化,添加一種新狀態F(Forward)。
當緩存行在多個高速緩存中存在副本時,如果某CPU加載了該行,在MESI協議中,其他S狀態的緩存都將應答此加載請求,引起總線消息冗餘。
爲了避免該問題,將其中一個緩存行修改爲F,由此副本負責應答。通常的最後一個加載的S將被置爲F。
3.4 寫-更新協議
除了寫-使無效協議外,還存在一類寫-更新協議。
當一個CPU修改一行時,更新它的全部緩存副本來保持一致性。這種協議與MESI的區別在於,針對處於共享狀態的行進行保存操作會產生一次總線事務,更新系統別處的緩存副本,並且更新主存。
4. 對開發的影響
在開發實踐中,對於競爭激烈的臨界資源,我們應當對它們進行填充,讓它們每個都佔用自己的一行。這樣做不但減少了初始訪問數據結構時導致的缺失,而且也防止了僞共享現象。當兩個或者兩個以上的臨界資源佔用同一行,而且它們被多個CPU同時使用時,就會出現僞共享。
Java中可以使用sun.misc.Contended註解進行填充,該註解在原生併發API多處被使用。如LongAdder的Cell類:
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
5. 參考資料
《現代體系結構上的UNIX系統》
《深入探索LINUX操作系統》