淺談僞共享

在多線程的程序中,咱們大部分都是從應用的級別關注共享資源的同步問題,對於底層的資源共享或者衝突問題不多關注,其實僞共享的問題在多線程中是一個效率的隱形殺手,由於不少時候咱們從代碼中並看不到這個問題的存在。html

何時產生僞共享

CPU的緩存系統都是以緩存行(cache line)爲單位存儲的, 緩存行是2的整數冪個連續字節(通常爲32~256個字節),最多見的緩存行大小是64個字節。當多線程程序同時修改的兩個變量存在在一個緩存行中時,就會引發緩存行級別的互斥,這就是僞共享。java

在CPU不一樣的核中緩存行的同步是經過MESI協議進行同步的,該協議能夠保證緩存數據的一致性,當不一樣核的cache數據不一致的時候就會引發數據的同步問題,同步主要是經過內存進行的,所以對效率的影響是比較大的。緩存

舉例說明:多線程

上圖說明了僞共享的問題。在覈1上運行的線程想更新變量X,同時心2上的線程想要更新變量Y,可是這兩個變量在同一個緩存行中。每一個線程都要去競爭緩存行的全部權來更新變量。若是核1得到了全部權,緩存子系統將會使核心2中對應的緩存行失效。當核2得到了全部權而後執行更新操做,核1就要使本身對應的緩存行失效。沒事失效後,若是再次使用數據就要從L3中從新獲取數據,這樣就會來回的通過L3緩存,大大影響了性能。若是互相競爭的核位於不一樣的插槽,就要額外橫跨插槽鏈接,問題可能更加嚴重。性能

避免僞共享

咱們能夠經過將不一樣的數據放在不一樣的緩存行就能夠避免僞共享的產生,咱們看下在java中是怎麼解決的 在JDK6中對於變量的緩存主要是經過增長變量來空間填充進行的,例如:this

//ConcurrentHashMapV8.java的實現
  public final static class VolatileLong { 
    public volatile long value = 0L; 
    public long p1, p2, p3, p4, p5, p6; // comment out 
  } 
複製代碼

對於HotSpot JVM中,在每一個對象中還有連個字的對象頭(4字節*2),所以上面的CounterCell就能夠看出分別是放到了兩個緩存行中,只索引是兩個緩存行主要是爲了適應64位的JVM,這樣能保證任何的兩個CounterCell都存放在不一樣的緩存行中。spa

在JDK8中主要經過註解的方式解決:線程

@sun.misc.Contended static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    ...
  }
複製代碼

性能比較

經過下面的例子真實的模擬下性能的差別翻譯

public final class FalseSharing implements Runnable {
    public final static int NUM_THREADS = 4; // change
    public final static long ITERATIONS = 500L * 1000L * 1000L;
    private final int arrayIndex;
 
    private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
    static {
        for (int i = 0; i < longs.length; i++) {
            longs[i] = new VolatileLong();
        }
    }
 
    public FalseSharing(final int arrayIndex) {
        this.arrayIndex = arrayIndex;
    }
 
    public static void main(final String[] args) throws Exception{
        final long start = System.nanoTime();
        runTest();
        System.out.println("duration = " + (System.nanoTime() - start));
    }
 
    private static void runTest() throws InterruptedException{
        Thread[] threads = new Thread[NUM_THREADS];
 
        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 = i;
        }
    }
 
    public final static class VolatileLong{
        public volatile long value = 0L;
        public long p1, p2, p3, p4, p5, p6; // comment out
    }
}
複製代碼

運行上面的代碼,增長線程數以及添加/移除緩存行的填充性能對好比下:code

從上圖可以明顯看出僞共享的影響,沒有緩存行競爭時,咱們幾近達到了隨着線程數的線性擴展。

上面的文章不少是翻譯的Martin Thompson的文章,地址以下: https://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html

相關文章
相關標籤/搜索