牛逼項目中用了Disruptor之后,性能提升了2.5倍

      網友投稿 742 2025-03-31

      https://jitwxs.cn/13836b16.html

      存儲設備往往是速度越快價格越昂貴,速度越快價格越低廉。在計算機中,CPU 的速度遠高于主存的速度,而主存的速度又遠高于磁盤的速度。為了解決不同存儲部件的速度不對等問題,讓高速設備充分發揮性能,引入了多級緩存機制。

      牛逼!項目中用了Disruptor之后,性能提升了2.5倍

      為了解決內存和 CPU 的速度不匹配問題,相繼引入了 L1 Cache、L2 Cache、L3 Cache,數字越小,容量越小,速度越快,位置越接近 CPU。

      現在的 CPU 都是由多個處理器,每個處理器由多個核心構成。一個處理器對應一個物理插槽,不同的處理器間通過 QPI 總線相連。一個處理器間的多核共享 L3 Cache。一個核包含寄存器、L1 Cache、L2 Cache,下圖是Intel Sandy Bridge CPU架構:

      緩存行與偽共享

      緩存中的數據并不是獨立的進行存儲的,它的最小存儲單位是緩存行,緩存行的大小是2的整數冪個字節,最常見的緩存行大小是 64 字節。CPU 為了執行的高效,會在讀取某個對象時,從內存上加載 64 的整數倍的長度,來補齊緩存行。

      以 Java 的 long 類型為例,它是 8 個字節,假設我們存在一個長度為 8 的 long 數組 arr,那么CPU 在讀取 arr[0] 時,首先查詢緩存,緩存沒有命中,緩存就會去內存中加載。由于緩存的最小存儲單位是緩存行,64 字節,且數組的內存地址是連續的,則將 arr[0] 到 arr[7] 加載到緩存中。后續 CPU 查詢 arr[6] 時候也可以直接命中緩存。

      現在假設多線程情況下,線程 A 的執行者 CPU Core-1 讀取 arr[1],首先查詢緩存,緩存沒有命中,緩存就會去內存中加載。從內存中讀取 arr[1] 起的連續的 64 個字節地址到緩存中,組成緩存行。由于從arr[1] 起,arr 的長度不足夠 64 個字節,只夠 56 個字節。假設最后 8 個字節內存地址上存儲的是對象 bar,那么對象 bar 也會被一起加載到緩存行中。

      現在有另一個線程 B,線程 B 的執行者 CPU Core-2 去讀取對象 bar,首先查詢緩存,發現命中了,因為 Core-1 在讀取 arr 數組的時候也順帶著把 bar 加載到了緩存中。

      這就是緩存行共享,聽起來不錯,但是一旦牽扯到了寫入操作就不妙了。

      假設 Core-1 想要更新 arr[7] 的值,根據 CPU 的 MESI 協議,那么它所屬的緩存行就會被標記為失效。因為它需要告訴其他的 Core,這個 arr[7] 的值已經被更新了,緩存已經不再準確了,你必須得重新去內存拉取。但是由于緩存的最小單元是緩存行,因此只能把 arr[7] 所在的一整行給標識為失效。

      此時 Core-2 就會很郁悶了,剛剛還能夠從緩存中讀取到對象 bar,現在再讀取卻被告知緩存行失效,必須得去內存重新拉取,延緩了 Core-2 的執行效率。

      這就是緩存偽共享問題,兩個毫無關聯的線程執行,一個線程卻因為另一個線程的操作,導致緩存失效。這兩個線程其實就是對同一緩存行產生了競爭,降低了并發性。

      Disruptor 緩存行填充

      Disruptor 為了解決偽共享問題,使用的方法是緩存行填充。這是一種以空間換時間的策略,主要思想就是通過往對象中填充無意義的變量,來保證整個對象獨占緩存行。

      舉個例子,以 Disruptor 中的 Sequence 為例,在 volatile long value 的前后各放置了 7 個 long 型變量,確保 value 獨占一個緩存行。

      public?class?Sequence?extends?RhsPadding?{?? ????private?static?final?long?VALUE_OFFSET;?? ?????? ????static?{?? ????????VALUE_OFFSET?=?UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));?? ????????...?? ????}?? ????...?? }?? ?? class?RhsPadding?extends?Value?{?? ????protected?long?p9,?p10,?p11,?p12,?p13,?p14,?p15;?? }?? ?? class?Value?extends?LhsPadding?{?? ????protected?volatile?long?value;?? }?? ?? class?LhsPadding?{?? ????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;?? }

      如下圖所示,其中 V 就是 Value 類的 value,P 為 value 前后填充的無意義 long 型變量,U 為其它無關的變量。不論什么情況下,都能保證 V 不和其他無關的變量處于同一緩存行中,這樣 V 就不會被其他無關的變量所影響。

      Padding 填充

      這里的 V 也不限定為 long 類型,其實只要對象的大小大于等于8個字節,通過前后各填充 7 個 long 型變量,就一定能夠保證獨占緩存行。

      此處以 Disruptor 的 RingBuffer 為例,最左邊的 7 個 long 型變量被定義在頂級父類 RingBufferPad 中,最右邊的 7 個 long 型變量被定義在 RingBuffer 的最后一行變量定義中,這樣所有的需要獨占的變量都被左右 long 型給包圍,確保會獨占緩存行。

      public?final?class?RingBuffer?extends?RingBufferFields?implements?Cursored,?EventSequencer,?EventSink?{?? ????public?static?final?long?INITIAL_CURSOR_VALUE?=?Sequence.INITIAL_VALUE;?? ????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;?? ????...?? }?? ?? abstract?class?RingBufferFields?extends?RingBufferPad?? {?? ????...?? }?? ?? abstract?class?RingBufferPad?{?? ????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;?? }

      @Contended

      在 JDK 1.8 中,提供了 @sun.misc.Contended 注解,使用該注解就可以讓變量獨占緩存行,不再需要手動填充了。注意,JVM 需要添加參數 -XX:-RestrictContended 才能開啟此功能。

      如果該注解被定義在了類上,表示該類的每個變量都會獨占緩存行;如果被定義在了變量上,通過指定 groupName,相同的 groupName 會獨占同一緩存行。

      //?類前加上代表整個類的每個變量都會在單獨的cache?line中?? @sun.misc.Contended?? public?class?ContendedData?{?? ????int?value;?? ????long?modifyTime;?? ????boolean?flag;?? ????long?createTime;?? ????char?key;?? }?? ?? //?同一?groupName?在同一緩存行?? public?class?ContendedGroupData?{?? ????@sun.misc.Contended("group1")?? ????int?value;?? ????@sun.misc.Contended("group1")?? ????long?modifyTime;?? ????@sun.misc.Contended("group2")?? ????boolean?flag;?? ????@sun.misc.Contended("group3")?? ????long?createTime;?? ????@sun.misc.Contended("group3")?? ????char?key;?? }

      @Contended 在 JDK 源碼中已經有所應用,以 Thread 類為例,為了保證多線程情況下隨機數的操作不會產生偽共享,相關的變量被設置為同一 groupName。

      public?class?Thread?implements?Runnable?{?? ????...?? ????//?The?following?three?initially?uninitialized?fields?are?exclusively?? ????//?managed?by?class?java.util.concurrent.ThreadLocalRandom.?These?? ????//?fields?are?used?to?build?the?high-performance?PRNGs?in?the?? ????//?concurrent?code,?and?we?can?not?risk?accidental?false?sharing.?? ????//?Hence,?the?fields?are?isolated?with?@Contended.?? ?? ????/**?The?current?seed?for?a?ThreadLocalRandom?*/?? ????@sun.misc.Contended("tlr")?? ????long?threadLocalRandomSeed;?? ?? ????/**?Probe?hash?value;?nonzero?if?threadLocalRandomSeed?initialized?*/?? ????@sun.misc.Contended("tlr")?? ????int?threadLocalRandomProbe;?? ?? ????/**?Secondary?seed?isolated?from?public?ThreadLocalRandom?sequence?*/?? ????@sun.misc.Contended("tlr")?? ????int?threadLocalRandomSecondarySeed;?? ?????? ????...?? }

      速度測試

      將 volatile long value 封裝為對象,四線程并行,每個線程循環 1 億次,對 value 進行更新操作,測試緩存行對速度的影響。

      CPU:AMD 3600 3.6 GHz,Memory:16 GB

      END

      看完本文有收獲?請轉發分享給更多人 關注「Java編程鴨」,提升Java技能 關注Java編程鴨微信公眾號,后臺回復:碼農大禮包?可以獲取最新整理的技術資料一份。涵蓋Java?框架學習、架構師學習等! 文章有幫助的話,在看,轉發吧。 謝謝支持喲 (*^__^*)

      Java

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:挖掘機生產制造哪家強?還是得看三一重工
      下一篇:Excel讓帶有小數點的數字以小數點對齊讓數據顯示更美觀
      相關文章
      亚洲中文字幕无码中文| 亚洲福利一区二区精品秒拍| 亚洲短视频在线观看| 亚洲乱码中文字幕久久孕妇黑人| 亚洲成A人片在线观看中文| 亚洲国产精品嫩草影院| 亚洲色最新高清av网站| 亚洲色图激情文学| 亚洲五月综合缴情婷婷| 亚洲精品国产精品国自产网站 | 亚洲六月丁香六月婷婷蜜芽| 在线免费观看亚洲| 久久精品国产亚洲av水果派| 亚洲电影中文字幕| 亚洲网站视频在线观看| 亚洲第一页中文字幕| 亚洲日韩在线视频| 久久国产亚洲精品| 亚洲欧洲国产综合AV无码久久| 亚洲欧美日韩自偷自拍| 含羞草国产亚洲精品岁国产精品| 小说专区亚洲春色校园| 亚洲AV无码一区二区乱子仑| 国产精品亚洲va在线观看| 亚洲精品tv久久久久| 中文字幕久久亚洲一区| 亚洲香蕉网久久综合影视| 亚洲av中文无码乱人伦在线r▽| 亚洲AV无码1区2区久久| 337p欧洲亚洲大胆艺术| 亚洲伊人久久大香线蕉| 伊人久久亚洲综合影院首页| 亚洲国产成人精品无码区花野真一| 亚洲avav天堂av在线网毛片| 亚洲第一页综合图片自拍| 亚洲国产精品嫩草影院久久| 亚洲一区二区三区AV无码| 亚洲AV无码1区2区久久| 亚洲AV无码乱码在线观看代蜜桃| 亚洲欧美成人综合久久久| 亚洲国产成人VA在线观看|