cpu指令重排序的原理


目錄:
  1.重排序場景
  2.追根溯源
  3.緩存一致性協議
  4.重排序原因

 

一、重排序場景

class ResortDemo {
    int a = 0;
    boolean flag = false;

    public void writer() {
        a = 1;                   //1
        flag = true;             //2
    }

    Public void reader() {
        if (flag) {                //3
            int i =  a * a;        //4
            ……
        }
    }
}  

當兩個線程 A 和 B,A 首先執行writer() 方法,隨后 B 線程接着執行 reader() 方法。線程B在執行操作4時,能否看到線程 A 在操作1對共享變量 a 的寫入?

答案是:不一定能看到。

由於操作1和操作2沒有數據依賴關系,編譯器和處理器可以對這兩個操作重排序;同樣,操作3和操作4沒有數據依賴關系,編譯器和處理器也可以對這兩個操作重排序。

二、追根溯源

  為了提升計算性能,CPU 從單核升級到了多核甚至用到了超線程技術最大化提高 CPU 的處理性能。CPU增加了高速緩存,操作系統增加了進程、線程,通過CPU時間片的切換最大化的提升CPU的使用率。
 
  通過高速緩存的存儲交互很好的解決了處理器與內存的速度矛盾,但是也為計算機系統帶來了更高的復雜度,因為它引入了一個新的問題,緩存一致性。

 三、緩存一致性協議

  同一份數據可能會被緩存到多個 CPU 中,如果在不同 CPU 中運行的不同線程看到同一份內存的緩存值不一樣就會存在緩存不一致的問題。為了達到數據訪問的一致,需要各個處理器在訪問緩存時遵循一些協議,在讀寫時根據協議來操作,常見的協議有MSI,MESI,MOSI 等。最常見的就是 MESI 協議。
接下來
給大家簡單講解一下 MESI。
  MESI 表示緩存行的四種狀態,分別是
  1. M(Modify) 表示共享數據只緩存在當前 CPU 緩存中,
並且是被修改狀態,也就是緩存的數據和主內存中的數
據不一致
  2. E(Exclusive) 表示緩存的獨占狀態,數據只緩存在當前
CPU 緩存中,並且沒有被修改
  3. S(Shared) 表示數據可能被多個 CPU 緩存,並且各個緩存中的數據和主內存數據一致
  4. I(Invalid) 表示緩存已經失效
 

                                             

                                          

 

 

                                                          

  對於 MESI 協議,從 CPU 讀寫角度來說會遵循以下原則:
  CPU 讀請求:緩存處於 M、E、S 狀態都可以被讀取,I 狀態 CPU 只能從主存中讀取數據。
  CPU 寫請求:緩存處於 M、E 狀態才可以被寫。對於 S 狀態的寫,需要將其他 CPU 中緩存行置為無效才可寫。

 四、重排序原因

  MESI 協議雖然可以實現緩存的一致性,但是也會存在一些問題。
  

 

   基於上圖中的原因,CPU又引入了storeBuffers的緩沖區。CPU0 只需要在寫入共享數據時,直接把數據寫入到 storebufferes 中,同時發送 invalidate 消息,然后繼續去處理其

他指令。當收到其他所有 CPU 發送了 invalidate acknowledge 消息時,再將 store bufferes 中的數據數據存儲至 cache line中。最后再從緩存行同步到主內存。        
                                                                  

 

     這個時候,我們再來看上述標題一中的重排序場景。

    class ResortDemo {
        int a = 0;
        boolean flag = false;

        public void writer() {
            a = 1;                   //1
            flag = true;             //2
        }

        Public void reader() {
            if (flag) {                //3
                int i =  a * a;        //4
            ……
            }
        }
    }  
  當執行1操作時,a的狀態從S->M,此時,線程A會先把變更寫入到storebuffers,然后發送invalidate去異步通知其他CPU線程,緊接着就執行了下面的2操作。
此時,可能1的變更還在storebuffers中,並未提交到主內存。什么時候會提交到主內存,也不確定。
所以,線程B調用read方法可能會出現,看到了flag的變更,但是看不到a的變更,就出現了重排序的現象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM