<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          太牛逼了!項(xiàng)目中用了Disruptor之后,性能提升了2.5倍

          共 4892字,需瀏覽 10分鐘

           ·

          2021-02-19 11:53

          往期熱門文章:
          1、最常用的分布式 ID 解決方案,都在這里了
          2、這 7 個(gè) Spring Boot 項(xiàng)目夠經(jīng)典!
          3、用 Java 擼了一款 SSH 客戶端
          4、處理 Exception 的幾種實(shí)踐,很優(yōu)雅,被很多團(tuán)隊(duì)采納!
          5、一致性協(xié)議算法-2PC、3PC、Paxos、Raft、ZAB、NWR詳解
          來源:https://jitwxs.cn/13836b16.html

          ?存儲設(shè)備往往是速度越快價(jià)格越昂貴,速度越快價(jià)格越低廉。在計(jì)算機(jī)中,CPU 的速度遠(yuǎn)高于主存的速度,而主存的速度又遠(yuǎn)高于磁盤的速度。為了解決不同存儲部件的速度不對等問題,讓高速設(shè)備充分發(fā)揮性能,引入了多級緩存機(jī)制。

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

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

          緩存行與偽共享

          緩存中的數(shù)據(jù)并不是獨(dú)立的進(jìn)行存儲的,它的最小存儲單位是緩存行,緩存行的大小是2的整數(shù)冪個(gè)字節(jié),最常見的緩存行大小是 64 字節(jié)。CPU 為了執(zhí)行的高效,會在讀取某個(gè)對象時(shí),從內(nèi)存上加載 64 的整數(shù)倍的長度,來補(bǔ)齊緩存行。

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

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

          現(xiàn)在有另一個(gè)線程 B,線程 B 的執(zhí)行者 CPU Core-2 去讀取對象 bar,首先查詢緩存,發(fā)現(xiàn)命中了,因?yàn)?Core-1 在讀取 arr 數(shù)組的時(shí)候也順帶著把 bar 加載到了緩存中。

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

          假設(shè) Core-1 想要更新 arr[7] 的值,根據(jù) CPU 的 MESI 協(xié)議,那么它所屬的緩存行就會被標(biāo)記為失效。因?yàn)樗枰嬖V其他的 Core,這個(gè) arr[7] 的值已經(jīng)被更新了,緩存已經(jīng)不再準(zhǔn)確了,你必須得重新去內(nèi)存拉取。但是由于緩存的最小單元是緩存行,因此只能把 arr[7] 所在的一整行給標(biāo)識為失效。

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

          這就是緩存?zhèn)喂蚕韱栴},兩個(gè)毫無關(guān)聯(lián)的線程執(zhí)行,一個(gè)線程卻因?yàn)榱硪粋€(gè)線程的操作,導(dǎo)致緩存失效。這兩個(gè)線程其實(shí)就是對同一緩存行產(chǎn)生了競爭,降低了并發(fā)性。

          Disruptor 緩存行填充

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

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

          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 為其它無關(guān)的變量。不論什么情況下,都能保證 V 不和其他無關(guān)的變量處于同一緩存行中,這樣 V 就不會被其他無關(guān)的變量所影響。

          Padding 填充

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

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

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

          abstract?class?RingBufferFields<E>?extends?RingBufferPad
          {
          ????...
          }

          abstract?class?RingBufferPad?{
          ????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;
          }

          @Contended

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

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

          //?類前加上代表整個(gè)類的每個(gè)變量都會在單獨(dú)的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 源碼中已經(jīng)有所應(yīng)用,以 Thread 類為例,為了保證多線程情況下隨機(jī)數(shù)的操作不會產(chǎn)生偽共享,相關(guān)的變量被設(shè)置為同一 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 封裝為對象,四線程并行,每個(gè)線程循環(huán) 1 億次,對 value 進(jìn)行更新操作,測試緩存行對速度的影響。

          CPU:AMD 3600 3.6 GHz,Memory:16 GB

          最近熱文閱讀:

          1、最常用的分布式 ID 解決方案,都在這里了
          2、這 7 個(gè) Spring Boot 項(xiàng)目夠經(jīng)典!
          3、用 Java 擼了一款 SSH 客戶端
          4、處理 Exception 的幾種實(shí)踐,很優(yōu)雅,被很多團(tuán)隊(duì)采納!
          5、一致性協(xié)議算法-2PC、3PC、Paxos、Raft、ZAB、NWR詳解
          6、這款I(lǐng)DEA插件刷爆了朋友圈,網(wǎng)友:一定是女朋友送的~
          7、@Autowire和@Resource注解使用的正確姿勢,別再用錯(cuò)的了!!
          8、Java中的Switch都支持String了,為什么不支持long?
          9、請謹(jǐn)慎使用Arrays.asList、ArrayList的subList
          10、人臉識別“抓”錯(cuò)了人,他在監(jiān)獄呆了 10 天
          關(guān)注公眾號,你想要的Java都在這里

          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天射天天撸 | 欧美成人午夜无码A片秀色直播 | 中文无码高清在线 | 国产内射视频在线观看 | 人成在线网站 |