溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Disruptor的共享與緩存是怎樣的

發(fā)布時(shí)間:2021-10-21 10:55:56 來源:億速云 閱讀:140 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(xì)講解有關(guān)Disruptor的共享與緩存是怎樣的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

什么是共享

下圖是計(jì)算的基本結(jié)構(gòu)。L1、L2、L3分別表示一級(jí)緩存、二級(jí)緩存、三級(jí)緩存,越靠近CPU的緩存,速度越快,容量也越小。所以L1緩存很小但很快,并且緊靠著在使用它的CPU內(nèi)核;L2大一些,也慢一些,并且仍然只能被一個(gè)單獨(dú)的CPU核使用;L3更大、更慢,并且被單個(gè)插槽上的所有CPU核共享;最后是主存,由全部插槽上的所有CPU核共享。

Disruptor的共享與緩存是怎樣的

圖3 計(jì)算機(jī)CPU與緩存示意圖

當(dāng)CPU執(zhí)行運(yùn)算的時(shí)候,它先去L1查找所需的數(shù)據(jù)、再去L2、然后是L3,如果最后這些緩存中都沒有,所需的數(shù)據(jù)就要去主內(nèi)存拿。走得越遠(yuǎn),運(yùn)算耗費(fèi)的時(shí)間就越長(zhǎng)。所以如果你在做一些很頻繁的事,你要盡量確保數(shù)據(jù)在L1緩存中。

另外,線程之間共享一份數(shù)據(jù)的時(shí)候,需要一個(gè)線程把數(shù)據(jù)寫回主存,而另一個(gè)線程訪問主存中相應(yīng)的數(shù)據(jù)。

下面是從CPU訪問不同層級(jí)數(shù)據(jù)的時(shí)間概念:

從CPU到大約需要的CPU周期大約需要的時(shí)間
主存 約60-80ns
QPI 總線傳輸(between sockets, not drawn) 約20ns
L3 cache約40-45 cycles約15ns
L2 cache約10 cycles約3ns
L1 cache約3-4 cycles約1ns
寄存器1 cycle 

可見CPU讀取主存中的數(shù)據(jù)會(huì)比從L1中讀取慢了近2個(gè)數(shù)量級(jí)。

緩存行

Cache是由很多個(gè)cache line組成的。每個(gè)cache line通常是64字節(jié),并且它有效地引用主內(nèi)存中的一塊兒地址。一個(gè)Java的long類型變量是8字節(jié),因此在一個(gè)緩存行中可以存8個(gè)long類型的變量。

CPU每次從主存中拉取數(shù)據(jù)時(shí),會(huì)把相鄰的數(shù)據(jù)也存入同一個(gè)cache line。

在訪問一個(gè)long數(shù)組的時(shí)候,如果數(shù)組中的一個(gè)值被加載到緩存中,它會(huì)自動(dòng)加載另外7個(gè)。因此你能非??斓谋闅v這個(gè)數(shù)組。事實(shí)上,你可以非??焖俚谋闅v在連續(xù)內(nèi)存塊中分配的任意數(shù)據(jù)結(jié)構(gòu)。

下面的例子是測(cè)試?yán)胏ache line的特性和不利用cache line的特性的效果對(duì)比。

package com.meituan.FalseSharing;
 
/**
 * @author gongming
 * @description
 * @date 16/6/4
 */
public class CacheLineEffect {
    //考慮一般緩存行大小是64字節(jié),一個(gè) long 類型占8字節(jié)
    static  long[][] arr;
 
    public static void main(String[] args) {
        arr = new long[1024 * 1024][];
        for (int i = 0; i < 1024 * 1024; i++) {
            arr[i] = new long[8];
            for (int j = 0; j < 8; j++) {
                arr[i][j] = 0L;
            }
        }
        long sum = 0L;
        long marked = System.currentTimeMillis();
        for (int i = 0; i < 1024 * 1024; i+=1) {
            for(int j =0; j< 8;j++){
                sum = arr[i][j];
            }
        }
        System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
 
        marked = System.currentTimeMillis();
        for (int i = 0; i < 8; i+=1) {
            for(int j =0; j< 1024 * 1024;j++){
                sum = arr[j][i];
            }
        }
        System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
    }
}

在2G Hz、2核、8G內(nèi)存的運(yùn)行環(huán)境中測(cè)試,速度差一倍。

結(jié)果: Loop times:30ms Loop times:65ms

什么是偽共享

ArrayBlockingQueue有三個(gè)成員變量: - takeIndex:需要被取走的元素下標(biāo) - putIndex:可被元素插入的位置的下標(biāo) - count:隊(duì)列中元素的數(shù)量

這三個(gè)變量很容易放到一個(gè)緩存行中,但是之間修改沒有太多的關(guān)聯(lián)。所以每次修改,都會(huì)使之前緩存的數(shù)據(jù)失效,從而不能完全達(dá)到共享的效果。

Disruptor的共享與緩存是怎樣的

圖4 ArrayBlockingQueue偽共享示意圖

如上圖所示,當(dāng)生產(chǎn)者線程put一個(gè)元素到ArrayBlockingQueue時(shí),putIndex會(huì)修改,從而導(dǎo)致消費(fèi)者線程的緩存中的緩存行無效,需要從主存中重新讀取。

這種無法充分使用緩存行特性的現(xiàn)象,稱為偽共享。

對(duì)于偽共享,一般的解決方案是,增大數(shù)組元素的間隔使得由不同線程存取的元素位于不同的緩存行上,以空間換時(shí)間。

package com.meituan.FalseSharing;
 
public class FalseSharing implements Runnable{
        public final static long ITERATIONS = 500L * 1000L * 100L;
        private int arrayIndex = 0;
 
        private static ValuePadding[] longs;
        public FalseSharing(final int arrayIndex) {
            this.arrayIndex = arrayIndex;
        }
 
        public static void main(final String[] args) throws Exception {
            for(int i=1;i<10;i++){
                System.gc();
                final long start = System.currentTimeMillis();
                runTest(i);
                System.out.println("Thread num "+i+" duration = " + (System.currentTimeMillis() - start));
            }
 
        }
 
        private static void runTest(int NUM_THREADS) throws InterruptedException {
            Thread[] threads = new Thread[NUM_THREADS];
            longs = new ValuePadding[NUM_THREADS];
            for (int i = 0; i < longs.length; i++) {
                longs[i] = new ValuePadding();
            }
            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();
            }
        }
 
        public void run() {
            long i = ITERATIONS + 1;
            while (0 != --i) {
                longs[arrayIndex].value = 0L;
            }
        }
 
        public final static class ValuePadding {
            protected long p1, p2, p3, p4, p5, p6, p7;
            protected volatile long value = 0L;
            protected long p9, p10, p11, p12, p13, p14;
            protected long p15;
        }
        public final static class ValueNoPadding {
            // protected long p1, p2, p3, p4, p5, p6, p7;
            protected volatile long value = 0L;
            // protected long p9, p10, p11, p12, p13, p14, p15;
        }
}

在2G Hz,2核,8G內(nèi)存, jdk 1.7.0_45 的運(yùn)行環(huán)境下,使用了共享機(jī)制比沒有使用共享機(jī)制,速度快了4倍左右。

結(jié)果: Thread num 1 duration = 447 Thread num 2 duration = 463 Thread num 3 duration = 454 Thread num 4 duration = 464 Thread num 5 duration = 561 Thread num 6 duration = 606 Thread num 7 duration = 684 Thread num 8 duration = 870 Thread num 9 duration = 823

把代碼中ValuePadding都替換為ValueNoPadding后的結(jié)果: Thread num 1 duration = 446 Thread num 2 duration = 2549 Thread num 3 duration = 2898 Thread num 4 duration = 3931 Thread num 5 duration = 4716 Thread num 6 duration = 5424 Thread num 7 duration = 4868 Thread num 8 duration = 4595 Thread num 9 duration = 4540

備注:在jdk1.8中,有專門的注解@Contended來避免偽共享,更優(yōu)雅地解決問題。

關(guān)于Disruptor的共享與緩存是怎樣的就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI