<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>

          Disruptor高性能之道-緩存行填充

          共 2749字,需瀏覽 6分鐘

           ·

          2022-03-02 01:44

          在上一篇文章中,我們介紹了Disruptor實現(xiàn)高性能的方式之一:環(huán)形數(shù)組RingBuffer。

          本文我們介紹 Disruptor高性能的另一個實現(xiàn)機(jī)制為 「“緩存行填充”」,它解決了CPU訪問內(nèi)存變量的“偽共享”問題。

          什么是偽共享?

          ?

          在解釋什么是偽共享之前,先了解下數(shù)據(jù)在緩存中是如何存儲的。

          ?

          我們都知道,計算機(jī)為了解決CPU與主存之間速度差的問題,引入了多級緩存機(jī)制。

          事實上,數(shù)據(jù)在CPU緩存(多級cache)中并非是單獨存儲的,而是按行存儲的。其中每一行成為一個緩存行。

          緩存行是CPU的Cache與主內(nèi)存進(jìn)行數(shù)據(jù)交換的基本單位,每個緩存行的大小一般為2的N次方字節(jié)。(「在32位計算機(jī)中為32字節(jié),64位計算機(jī)中為64字節(jié)?!?/strong>)可以想到,如果計算機(jī)為128位,則緩存行大小就是128字節(jié)。

          在Java中,一個long型變量為8字節(jié),也就是說在64位計算機(jī)中,每行可存放8個long型變量。

          ?

          當(dāng)CPU訪問某個變量的時,如果CPU Cache中存在該變量,則直接獲取。若不存在則去主內(nèi)存獲取該變量。由于緩存行機(jī)制的存在,因此會將該變量所在內(nèi)存區(qū)域為一個緩存行大小的內(nèi)存復(fù)制到CPU Cache中。

          ?

          此時有可能會在一行緩存行中加載多個變量,如圖中不同的顏色對應(yīng)不同的long型變量。

          ?

          試想,如果多個內(nèi)核的線程都操作了同一緩存行的數(shù)據(jù),如圖所示。CPU1讀取并修改了緩存行中的變量D,了解volatile的同學(xué)都知道,當(dāng)CPU Cache中的變量發(fā)生變更,會通過緩存一致性協(xié)議通知其他CPU失效當(dāng)前緩存行,重新從主內(nèi)存中加載當(dāng)前行的值。

          ?

          圖中,CPU1修改了緩存行中的變量D,CPU2也在讀取該緩存行的值。根據(jù)緩存一致性協(xié)議,CPU2中的緩存行會失效,因為它操作的緩存行中的變量D的值已經(jīng)不是最新值了。

          這是因為CPU是以緩存行為單位進(jìn)行數(shù)據(jù)的讀寫操作的。

          這就是偽共享。

          為什么是“偽”共享呢?

          ?

          看起來CPU1 與 CPU2 共享了同一個緩存行,但是由于CPU以緩存行為單位進(jìn)行讀寫操作,無論CPU1 與 CPU2中的任何一位修改了緩存行中的值,都需要通知其他CPU對失效該緩存行。也就是說當(dāng)線程對緩存進(jìn)行了寫操作,則當(dāng)前線程所在內(nèi)核就需要失效其他內(nèi)核的緩存行,并重新加載主內(nèi)存。

          ?

          這是一種緩存未命中的情況,當(dāng)發(fā)生這樣的情況,緩存本身的意義就被削弱了,因為CPU始終需要從主內(nèi)存加載數(shù)據(jù),而根本命中不了CPU Cache中的緩存。

          ?

          所謂的“偽”共享,就可以理解成是一種 “錯誤”的共享,這種共享如果不發(fā)生,則多核CPU操作緩存行互不影響,每個核心都只關(guān)心自己操作的變量,而不會因為讀寫自己關(guān)心的變量而影響到其他CPU對變量的讀寫。

          ?

          Disruptor是如何進(jìn)行緩存行填充的?

          ?

          Disruptor解決偽共享的方式為:使用緩存行填充。

          ?

          上文我們提到,由于多核CPU同時讀寫統(tǒng)一緩存行中的數(shù)據(jù),導(dǎo)致了CPU Cache命中失敗的偽共享問題。

          那么只需要避免多核CPU同時操作統(tǒng)一緩存行,不就可以解決這個問題了么?

          事實上,Disruptor正是這么做的。

          ?

          Disruptor為Sequence中的value(volatile修飾)進(jìn)行了緩存行填充,保證每個sequence只在一個緩存行中存在,避免了其他的變量對sequence的干擾。

          ?


          class?LhsPadding
          {
          ????protected?long?p1,?p2,?p3,?p4,?p5,?p6,?p7;??//?緩存行填充
          }

          class?Value?extends?LhsPadding
          {
          ????protected?volatile?long?value;
          }

          class?RhsPadding?extends?Value
          {
          ????protected?long?p9,?p10,?p11,?p12,?p13,?p14,?p15;?//?緩存行填充
          }

          /**
          ?*?

          Concurrent?sequence?class?used?for?tracking?the?progress?of
          ?*?the?ring?buffer?and?event?processors.??Support?a?number
          ?*?of?concurrent?operations?including?CAS?and?order?writes.
          ?*
          ?*?

          Also?attempts?to?be?more?efficient?with?regards?to?false
          ?*?sharing?by?adding?padding?around?the?volatile?field.
          ?*/
          public?class?Sequence?extends?RhsPadding
          {
          ????static?final?long?INITIAL_VALUE?=?-1L;
          ????private?static?final?Unsafe?UNSAFE;
          ????private?static?final?long?VALUE_OFFSET;

          ????static
          ????{
          ????????UNSAFE?=?Util.getUnsafe();
          ????????try
          ????????{
          ????????????VALUE_OFFSET?=?UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
          ????????}
          ????????catch?(final?Exception?e)
          ????????{
          ????????????throw?new?RuntimeException(e);
          ????????}
          ????}

          其他的緩存行填充機(jī)制

          JDK1.8 提供了注解 「@Contended」 用于解決偽共享問題,需要注意的是,如果業(yè)務(wù)代碼需要使用該注解,要添加JVM參數(shù)

          ?

          -XX:-RestrictContended。

          ?

          默認(rèn)填充寬度為128,若需要自定義填充寬度,則設(shè)置

          ?

          -XX:ContendedPaddingWidth

          ?

          具體的使用方式為:

          @sun.misc.Contended
          public?final?static?class?Value?{
          ??public?volatile?long?value?=?0L;
          }

          參考資料

          • 《Java并發(fā)編程之美》
          • 并發(fā)編程網(wǎng):剖析Disruptor:為什么會這么快?(二)神奇的緩存行填充


          瀏覽 133
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  蜜芽AV无码 | 免费观看一级二级网站 | 伊人大香蕉在线观看视频 | 天天看A片 | 国产高清无码一级片 |