CPU的亂序執行和合並寫技術

CPU的亂序執行:

  CPU在進行讀等待的同時執行指令,是CPU亂序的根源,這其實不是亂,而是提高效率。例如指令1去內存讀數據,因爲CPU與內存訪問速度相差100倍,如果指令2的執行過程不需要依賴指令1,那麼指令2可以先執行,亂序執行的本質是同時執行。Java對象的創建過程不是一個原子操作,極有可能出現指令重排序,下面通過Java對象創建的彙編碼講解。

// 源碼:
class T { int num = 8;}
T t = new T();

// 彙編碼:
0 new #2 <T>                    ---> new了一塊內存,對象屬性num賦初始值(03 dup                           ---> 複製棧中的引用,供下面的invokespecial消耗
4 invokespecial #3 <T.<init>>   ---> 執行構造方法,對象屬性num賦默認值(8),將堆中的對象地址與棧中的引用建立關聯
7 astore_1                      ---> 將棧中的引用彈出賦值局部變量表的第一個位置,第0個位置是this
8 return

在這裏插入圖片描述

DCL指令重排序

  這也是DCL(Double Check Lock)單例必須要加上volatile關鍵字的原因,CPU層面使用內存屏障禁止指令重排序,通過在指令1和指令2之間插入內存屏障來禁止指令重排序,Inter通過原語lfence(load), sfence(save), mfence(mixed)實現內存屏障,當然也可以使用總線鎖來解決。

  1. sfence:在sfence指令前的寫操作必須在sfence指令後的寫操作前完成;
  2. lfence:在lfence指令前的讀操作必須在lfence指令後的讀操作前完成;
  3. mfence:在mfence指令前的讀寫操作必須在mfence指令後的讀寫操作前完成;
  4. lock:原子指令,如x86上的lock...指令是一個Full Barrier,執行時會鎖住內存子系統來確保執行順序,甚至跨越多個CPU,這是硬件層次.
  5. volatile:locl addl 0x0(exp),向exp寄存器中加0,主要是執行lock指令;
  6. sychronized:lock comxchg,通過自旋獲得鎖才能執行後面的操作;
// DCL單例
public class DCLInstance{
    private static volatile DCLInstance instance = null;
    private DCLInstance(){}
    
    public static DCLInstance getInstance(){
        if(instance==null){
            sychronized(DCLINstance.class){
                if(instance==null){
                    instance = new DCLINstance();
                }
            }
        }
        return instance;
    }
}

Write Combining 合併寫技術:

  Write Combining Buffer一般是4個字節,由於ALU速度太快,爲了提高寫效率,CPU在寫入L1時,寫入一個WC Buffer,當WC Buffer滿了之後,直接用WC寫入L2。
在這裏插入圖片描述

Writer Combing

  可以通過程序對合並寫技術進行驗證,如下所示程序,runCaseOne中將7次寫入操作一次性執行,runCaseTwo中將寫操作分爲兩組,每組4次寫操作,一共8次寫操作,但是runCaseTwo的執行耗時卻比runCaseOne要少。

public class WriteCombining {
    private static final int ITERATIONS = Integer.MAX_VALUE;
    private static final int ITEMS = 1 << 24;
    private static final int MASK = ITEMS - 1;
    private static final byte[] arrayA = new byte[ITEMS];
    private static final byte[] arrayB = new byte[ITEMS];
    private static final byte[] arrayC = new byte[ITEMS];
    private static final byte[] arrayD = new byte[ITEMS];
    private static final byte[] arrayE = new byte[ITEMS];
    private static final byte[] arrayF = new byte[ITEMS];

    public static long runCaseOne() {
        long start = System.nanoTime();
        int i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayA[slot] = b;
            arrayB[slot] = b;
            arrayC[slot] = b;
            arrayD[slot] = b;
            arrayE[slot] = b;
            arrayF[slot] = b;
        }
        return System.nanoTime() - start;
    }

    public static long runCaseTwo() {
        long start = System.nanoTime();
        int i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayA[slot] = b;
            arrayB[slot] = b;
            arrayC[slot] = b;
        }
        i = ITERATIONS;
        while (--i != 0) {
            int slot = i & MASK;
            byte b = (byte) i;
            arrayD[slot] = b;
            arrayE[slot] = b;
            arrayF[slot] = b;
        }
        return System.nanoTime() - start;
    }
    
    public static void main(final String[] args) {
        System.out.println("單次執行 (ms) = " + runCaseOne()/100_0000);
        System.out.println("拆分兩次執行 (ms) = " + runCaseTwo()/100_0000);
    }
}

// 輸出:
單次執行 (ms) = 4682
拆分兩次執行 (ms) = 4462
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章