您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Disruptor的共享與緩存是怎樣的,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
下圖是計算的基本結構。L1、L2、L3分別表示一級緩存、二級緩存、三級緩存,越靠近CPU的緩存,速度越快,容量也越小。所以L1緩存很小但很快,并且緊靠著在使用它的CPU內核;L2大一些,也慢一些,并且仍然只能被一個單獨的CPU核使用;L3更大、更慢,并且被單個插槽上的所有CPU核共享;最后是主存,由全部插槽上的所有CPU核共享。
圖3 計算機CPU與緩存示意圖
當CPU執行運算的時候,它先去L1查找所需的數據、再去L2、然后是L3,如果最后這些緩存中都沒有,所需的數據就要去主內存拿。走得越遠,運算耗費的時間就越長。所以如果你在做一些很頻繁的事,你要盡量確保數據在L1緩存中。
另外,線程之間共享一份數據的時候,需要一個線程把數據寫回主存,而另一個線程訪問主存中相應的數據。
下面是從CPU訪問不同層級數據的時間概念:
從CPU到 | 大約需要的CPU周期 | 大約需要的時間 |
---|---|---|
主存 | 約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讀取主存中的數據會比從L1中讀取慢了近2個數量級。
Cache是由很多個cache line組成的。每個cache line通常是64字節,并且它有效地引用主內存中的一塊兒地址。一個Java的long類型變量是8字節,因此在一個緩存行中可以存8個long類型的變量。
CPU每次從主存中拉取數據時,會把相鄰的數據也存入同一個cache line。
在訪問一個long數組的時候,如果數組中的一個值被加載到緩存中,它會自動加載另外7個。因此你能非常快的遍歷這個數組。事實上,你可以非常快速的遍歷在連續內存塊中分配的任意數據結構。
下面的例子是測試利用cache line的特性和不利用cache line的特性的效果對比。
package com.meituan.FalseSharing; /** * @author gongming * @description * @date 16/6/4 */ public class CacheLineEffect { //考慮一般緩存行大小是64字節,一個 long 類型占8字節 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內存的運行環境中測試,速度差一倍。
結果: Loop times:30ms Loop times:65ms
ArrayBlockingQueue有三個成員變量: - takeIndex:需要被取走的元素下標 - putIndex:可被元素插入的位置的下標 - count:隊列中元素的數量
這三個變量很容易放到一個緩存行中,但是之間修改沒有太多的關聯。所以每次修改,都會使之前緩存的數據失效,從而不能完全達到共享的效果。
圖4 ArrayBlockingQueue偽共享示意圖
如上圖所示,當生產者線程put一個元素到ArrayBlockingQueue時,putIndex會修改,從而導致消費者線程的緩存中的緩存行無效,需要從主存中重新讀取。
這種無法充分使用緩存行特性的現象,稱為偽共享。
對于偽共享,一般的解決方案是,增大數組元素的間隔使得由不同線程存取的元素位于不同的緩存行上,以空間換時間。
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內存, jdk 1.7.0_45 的運行環境下,使用了共享機制比沒有使用共享機制,速度快了4倍左右。
結果: 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后的結果: 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來避免偽共享,更優雅地解決問題。
關于Disruptor的共享與緩存是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。