共享Java緩存

      網(wǎng)友投稿 905 2022-05-29

      對于多線程編程來說,一般要注意線程安全的問題,如果是要實現(xiàn)超高并發(fā)的中間件,特別是需要多線程處理列表、數(shù)組和隊列的時候,就需要注意偽共享的問題。否則可能無法發(fā)揮多線程的優(yōu)勢,性能可能比單線程還差。

      偽共享和Java緩存行

      偽共享

      介紹偽共享前先說說 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)容。

      上一篇:Pytorch筆記搬運:張量
      下一篇:《Spark數(shù)據(jù)分析:基于Python語言 》 —1.1.2 Hadoop簡介
      相關(guān)文章
      亚洲色爱图小说专区| 亚洲免费视频播放| 亚洲偷自拍另类图片二区| 亚洲熟妇无码另类久久久| 九九精品国产亚洲AV日韩| 亚洲日韩中文字幕一区| 亚洲熟妇成人精品一区| 国产午夜亚洲精品| 亚洲色大18成人网站WWW在线播放| 亚洲av乱码一区二区三区| 亚洲日韩乱码久久久久久| 亚洲网站在线播放| 亚洲婷婷在线视频| 亚洲午夜精品一区二区公牛电影院 | 久久亚洲熟女cc98cm| 久久精品国产亚洲AV大全| 久久久国产精品亚洲一区| 91亚洲国产在人线播放午夜| 亚洲精品日韩专区silk| 国产亚洲色婷婷久久99精品91| 国产亚洲无线码一区二区| 亚洲精品乱码久久久久66| 亚洲AV无码成人精品区在线观看 | 国产精品亚洲а∨无码播放| 亚洲中文字幕无码久久综合网| 国产精品亚洲高清一区二区| 日韩精品亚洲aⅴ在线影院| 亚洲人成77777在线播放网站| 亚洲午夜日韩高清一区| 亚洲午夜国产精品无码老牛影视 | 亚洲黄色网址在线观看| 亚洲婷婷综合色高清在线| 亚洲日本在线播放| 亚洲综合无码无在线观看| 亚洲AV无码一区二区三区久久精品 | 久久久久无码专区亚洲av | 在线观看亚洲AV日韩A∨| 国产AV无码专区亚洲AV蜜芽| 亚洲日本一区二区三区在线不卡| 亚洲乳大丰满中文字幕| 亚洲国产综合精品中文第一区|