偽共享和Java緩存行
對于多線程編程來說,一般要注意線程安全的問題,如果是要實現(xiàn)超高并發(fā)的中間件,特別是需要多線程處理列表、數(shù)組和隊列的時候,就需要注意偽共享的問題。否則可能無法發(fā)揮多線程的優(yōu)勢,性能可能比單線程還差。
偽共享
介紹偽共享前先說說 SMP、Cache、MESI 幾個概念。
SMP 系統(tǒng)
操作系統(tǒng)主要分下面兩種:
SMP——Symmetric Multi-Processing (SMP),即對稱多處理器結(jié)構(gòu)
AMP——Asymmetric Multi-Processing (AMP) ,非對稱多處理器結(jié)構(gòu)
SMP的特征是:只有一個操作系統(tǒng)實例,運行在多個CPU上,每個CPU的結(jié)構(gòu)都是一樣的,內(nèi)存、資源共享。這種系統(tǒng)有一個最大的特點就是共享所有資源。
AMP的特征是:多個CPU,各個CPU在架構(gòu)上不一樣,每個CPU內(nèi)核運行一個獨立的操作系統(tǒng)或同一操作系統(tǒng)的獨立實例,每個CPU擁有自己的獨立資源。這種結(jié)構(gòu)最大的特點在于不共享資源。
我們平時使用的機器基本都是 SMP 系統(tǒng)。
Cache
CPU 和主內(nèi)存之間的運算速度是差異巨大的,在現(xiàn)今的 SMP 系統(tǒng) 中,會在 CPU 和主存間設(shè)置三級高速緩存,L1、L2 和 L3,讀取順序由先到后。可以簡單理解為,L1 Cache分為指令緩存和數(shù)據(jù)緩存兩種,L2 Cache只存儲數(shù)據(jù),L1 和 L2 都是每個核心都有,而 L3 被多核共享。
緩存系統(tǒng)中是以緩存行(cache line)為單位存儲的。緩存行是2的整數(shù)冪個連續(xù)字節(jié),一般為32-256個字節(jié)。最常見的緩存行大小是64個字節(jié)。
MESI
MESI 是一致性協(xié)議,研究過 Java volatile 可能會比較熟悉,因為L1 L2是每個核心自己使用,而L3一般是多核共享,而不同核心又可能涉及共享變量問題,所以各個高速緩存間勢必會有一致性的問題。MESI就是解決這些問題的一種協(xié)議或規(guī)范。
下面是關(guān)于 MESI 的一段說明:
在MESI協(xié)議中,每個Cache line有4個狀態(tài),可用2個bit表示,它們分別是:
M(Modified):這行數(shù)據(jù)有效,數(shù)據(jù)被修改了,和內(nèi)存中的數(shù)據(jù)不一致,數(shù)據(jù)只存在于本Cache中;
E(Exclusive):這行數(shù)據(jù)有效,數(shù)據(jù)和內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)只存在于本Cache中;
S(Shared):這行數(shù)據(jù)有效,數(shù)據(jù)和內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)存在于很多Cache中;
I(Invalid):這行數(shù)據(jù)無效。
什么是偽共享
到現(xiàn)在什么是偽共享,為什么它會影響到性能呢? 先看一個圖:
從圖中可看到,thread0,thread1 分別由 core0,core1 調(diào)度,兩線程都想更新彼此獨立的兩個變量,但是由于兩個變量位于同一個cache line中,根據(jù)MESI cache line 的狀態(tài)應(yīng)該都是 Shared,而對于同一 cache line 的操作,core 間必須爭奪主導(dǎo)權(quán)(ownership),如果 core0 搶到了,thread0 因此去更新cache line,會導(dǎo)致core1中的 cache line 狀態(tài)變?yōu)?Invalid,隨后 thread1 去更新時必須通知 core0 將 cache line 刷回主存,然后它再從主內(nèi)存中 load 該 cache line 進高速緩存之后再進行修改,但該修改又會使得 core0 的 cache line 失效,thread0 重復(fù)上演 thread1 歷史,這樣導(dǎo)致了高速緩存并未起到應(yīng)有的作用,反而影響了性能。
這就是對稱多處理器(SMP)系統(tǒng)中一個著名的性能問題:偽共享。
Java 緩存行
對于出現(xiàn)偽共享的問題,根據(jù)上文介紹出現(xiàn)的原因,我們可以采用填充的方式來保證某個熱點對象被隔離在不同的緩存行中,從而避免了多線程互相搶同一個 cache line,這樣性能也就不會造成影響。
Java 8 方案
Java的各個版本在減少偽共享的做法都有區(qū)別,Java 8 以前的版本可以采用填充的方案,這里只具體介紹 Java 8 的實現(xiàn)方案。
JAVA 8中添加了一個 @Contended 的注解,對某字段加上該注解則表示該字段會單獨占用一個緩存行(Cache Line)。
舉個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
public final class FalseSharing implements Runnable { public static int NUM_THREADS = 4; public final static long ITERATIONS = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong[] longs; public FalseSharing(final int arrayIndex) { this.arrayIndex = arrayIndex; } @Override public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; } } public static void main(final String[] args) throws Exception { System.out.println("starting...."); longs = new VolatileLong[NUM_THREADS]; for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong(); } final long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new FalseSharing(i)); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } }
1 2 3 4
@Contended public class VolatileLong { public volatile long value = 0L; }
這個例子是兩個線程同時對同一個數(shù)組進寫操作,見 runTest 函數(shù) 和 FalseSharing 類 的 run 函數(shù)。VolatileLong 類的 value 被 volatile 修飾,
在 run 函數(shù)中沒有線程安全的問題。
測試情況:
當在 VolatileLong 類上加了 @Contended 注解時,輸出:
duration = 3581736100
當把 VolatileLong 類上的 @Contended 注解刪除時,輸出:
duration = 20545682900
可以看到,不加@Contended 注解時,所消耗的時間大概是加 @Contended 注解 時的5倍。
注意 @Contended 注解要生效,需要加上虛擬機參數(shù) -XX:-RestrictContended。
對于偽共享的問題呢,解決方案本質(zhì)上就是填充,某種程度就是以空間換時間,這值得我們?nèi)ニ伎肌?/p>
偽共享的問題是程序性能的問題,雖然很重要,但優(yōu)先級不要拔高,不要過早優(yōu)化。
Java
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。