指令重排序和內存屏障

1、指令重排序

  指令重排序分爲三種,分別爲編譯器優化重排序指令級並行重排序內存系統重排序。如圖所示,後面兩種爲處理器級別(即爲硬件層面)。html

  • 編譯器優化重排序:編譯器在不改變程序執行結果的狀況下,爲了提高效率,對指令進行亂序的編譯。例如在代碼中A操做須要獲取其餘資源而進入等待的狀態,而A操做後面的代碼跟其沒有依賴關係,若是編譯器一直等待A操做完成再往下執行的話效率要慢的多,因此能夠先編譯後面的代碼,這樣的亂序能夠提高不小的編譯速度。
  • 指令級並行重排序:處理器在不影響程序執行結果的狀況下,將多條指令重疊在一塊兒執行,一樣也是爲了提高效率。
  • 內存系統重排序:這個跟以前兩個不一樣的是,其爲僞重排序,也就是說只是看起來像在亂序執行而已。對於現代的處理器來講,在CPU和主內存之間都具有一個高速緩存,高速緩存的做用主要爲減小CPU和主內存的交互(CPU的處理速度要快的多),在CPU進行讀操做時,若是緩存沒有的話從主內存取,而對於寫操做都是先寫在緩存中,最後再一次性寫入主內存,緣由是減小跟主內存交互時CPU的短暫卡頓,從而提高性能,可是延時寫入可能會致使一個問題——數據不一致。
    // CPU1執行如下操做
    a = 1; int i = b; // CPU2執行下面操做 b = 1; int j = a;

    其執行圖以下:
        

     

         從上面圖中咱們能夠看到,對於CPU來講,先將a = 1寫入緩存在讀取變量b,事後在寫入a到主內存,而這個操做從表面上看就變成了先讀取變量b,在寫入a到主內存,也就是發生了重排序,因此才說這爲僞重排序。緩存

            而從上面咱們也能夠看出,因爲CPU1和2寫入的時機不一樣,最終可能致使讀到的(a,b)變量有四種狀況,分別是(0,0),(0,1),(1,0),(1,1)。例如,在兩個緩存未寫入主內存的時候就進行變量讀取,這時候讀到的就爲(0,0),其餘狀況類推。因此Java在實現內存模型的時候會禁止特定類型的重排序。性能

 

   as-if-serial語義:這是重排序都須要遵循的規則,其大體意思就是在單線程中,只要不改變程序的最終執行結果,那麼爲了提高性能能夠改變指令執行的順序。優化

2、內存屏障

  在編譯器方面使用volatile關鍵字能夠禁止指令重排序,而在硬件方面實現禁止指令重排序的則是內存屏障。其中包括硬件層原本就有的LoadBarriers和StoreBarriers 和JVM封裝實現的四種內存屏障。spa

   從硬件層上

    內存屏障分爲兩種,LoadBarriers和StoreBarriers。線程

    • LoadBarriers:在執行屏障後一個操做前,保證已經刷新了緩存的數據,也就是說使緩存失效,強制從內存刷新數據到緩存中。
      i = a; LoadBarriers; // ..其餘操做

      如上僞代碼中,在執行其餘操做以前必須保證a的變量從主內存中讀取而且刷新到緩存中。code

    • StoreBarriers:此屏障以前的寫入緩存中的數據同步到內存中,而且保證其餘線程可見。
      a = 1; b = 2; c = 3; StoreBarriers; // ..其餘操做

      如上僞代碼中,保證在其餘操做以前,寫入緩存中的a,b,c三個變量同步到主內存中,而且其餘線程能夠觀察到變量的變化。htm

  JVM實現的內存屏障

  1. LoadLoad:對於Load1;LoadLoad;Load2這樣的狀況,保證Load1先於Load2及以後的Load操做,且對其可見。例如:
    ... int i = a; LoadLoad; int j = b;

    在這段代碼中,在int j = b以及後面的Load操做中,都能見到int i = a的操做,也就是int i = a先於後面的讀取操做。即,禁止int i = a和以後的讀操做重排序。blog

  2. LoadStore:對於Load1;LoadStore;Store1來講,保證Load1操做先於Store1以及後面的Store操做,即對後Store操做可見。如:
    int i = a; LoadStore b = 1;

    // int i = a對於b = 1及以後的store操做都可見。
  3. StoreLoad:同上,Store1;StoreLoad;Load1狀況來講,保證Store1操做先於後續的全部Load操做,而且其Store的變量操做對其餘處理器可見。因爲Store操做會當即刷新到內存並對其餘處理器緩存可見的特性,其具有其餘三個屏障的功能,可是相對的,其花費的開銷較大。
  4. StoreStore:在Store1;StoreStore;Store2狀況中,保證Store1操做先於Store2操做,即在Store1後續的Store操做以前,Store1操做保證刷新到內存而且對其餘處理器可見。

  volatile的禁止指令重排序

    咱們都知道volatile關鍵字有兩個語義:排序

    • 保證內存可見性
    • 禁止指令重排序

    其中JVM對其禁止指令重排序在硬件層面的實現就是經過在volatile修飾的變量先後插入內存屏障。volatile變量的內存屏障規則以下:

  在每一個volatile寫操做前插入StoreStore屏障,在寫操做後插入StoreLoad屏障;
  在每一個volatile讀操做前插入LoadLoad屏障,在讀操做後插入LoadStore屏障;

    而在編譯器方面則是由於對於volatile變量內存中的六種操做會有特殊的規則,能夠看看個人另外一篇文章——淺談內存模型,裏面介紹了volatile兩種語義的原理,同時也說明了volatile關鍵字沒有原子性的緣由。

 

 

 文章如有不正之處,還望指出,在此多謝!

原文出處:https://www.cnblogs.com/zhangweicheng/p/11674660.html

相關文章
相關標籤/搜索