重排序與數據依賴性

爲何須要重排序

如今的CPU通常採用流水線來執行指令。一個指令的執行被分紅:取指、譯碼、訪存、執行、寫回、等若干個階段。而後,多條指令能夠同時存在於流水線中,同時被執行。java

指令流水線並非串行的,並不會由於一個耗時很長的指令在「執行」階段呆很長時間,而致使後續的指令都卡在「執行」以前的階段上。咱們編寫的程序都要通過優化後(編譯器和處理器會對咱們的程序進行優化以提升運行效率)纔會被運行,優化分爲不少種,其中有一種優化叫作重排序,重排序須要遵照as-if-serial規則和happens-before規則,不能說你想怎麼排就怎麼排,若是那樣豈不是亂了套。重排序的目的是爲了性能緩存

Example:安全

過程A:cpu0—寫入1—> bank0;過程B:cpu0—寫入2—> bank1;若是bank0狀態爲busy, 則A過程須要等待若是進行重排序,則直接能夠先執行B過程。

重排序分類

通常重排序能夠分爲以下三種:微信

  1. 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序;多線程

  2. 指令級並行的重排序。現代處理器採用了指令級並行技術來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序;app

  3. 內存系統的重排序。因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行的。性能

重排序過程

一個好的內存模型實際上會放寬對處理器和編譯器規則的束縛,也就是說軟件技術和硬件技術都爲同一個目標而進行奮鬥:在不改變程序執行結果的前提下,儘量提升並行度。Java內存模型(JMM)對底層儘可能減小約束,使其可以發揮自身優點。所以,在執行程序時,爲了提升性能,編譯器和處理器經常會對指令進行重排序優化

如圖,1屬於編譯器重排序,而2和3統稱爲處理器重排序。這些重排序會致使線程安全的問題,一個很經典的例子就是DCL(雙重檢驗鎖)問題,這個在之後的文章中會具體去聊。針對編譯器重排序,Java內存模型(JMM)的編譯器重排序規則會禁止一些特定類型的編譯器重排序;針對處理器重排序,編譯器在生成指令序列的時候會經過插入內存屏障指令來禁止某些特殊的處理器重排序spa

那麼什麼狀況下,不能進行重排序了?下面就來講說數據依賴性。有以下代碼:.net


double pi = 3.14 //Adouble r = 1.0 //Bdouble area = pi * r * r //C

這是一個計算圓面積的代碼,因爲A,B之間沒有任何關係,對最終結果也不會存在影響,它們之間執行順序能夠重排序。所以執行順序能夠是A->B->C或者B->A->C執行最終結果都是3.14,即A和B之間沒有數據依賴性。具體的定義爲:若是兩個操做訪問同一個變量,且這兩個操做有一個爲寫操做,此時這兩個操做就存在數據依賴性,這裏就存在三種狀況:1. 讀後寫;2.寫後寫;3. 寫後讀,或者三種操做都是存在數據依賴性的,若是重排序會對最終執行結果產生影響,編譯器和處理器在重排序時,會遵照數據依賴性,編譯器和處理器不會改變存在數據依賴性關係的兩個操做的執行順序

重排序對多線程的影響

class ReorderExample {int a = 0;boolean flag = false;
public void writer() { a = 1; //1 flag = true; //2 }
public void reader() {if (flag) { //3int i = a * a; //4 …… } }}

flag爲標誌位,表示a有沒有被寫入,當A線程執行 writer 方法,B線程執行 reader 方法,線程B在執行4操做的時候,可否看到線程A對a的寫入操做?

答案是:不必定!

因爲操做1和操做2沒有數據依賴關係,編譯器和處理器能夠對這兩個操做重排序。

若是操做1和操做2作了重排序,程序執行時,線程A首先寫標記變量 flag,隨後線程 B 讀這個變量。因爲條件判斷爲真,線程 B 將讀取變量a。此時,變量 a 還根本沒有被線程 A 寫入,在這裏多線程程序的語義被重排序破壞了

數據依賴性

若是兩個操做訪問同一個變量,且這兩個操做中有一個爲寫操做,此時這兩個操做之間就存在數據依賴性。數據依賴分下列三種類型:

名稱 代碼示例 說明
寫後讀 a = 1;b = a; 寫一個變量以後,再讀這個位置。
寫後寫 a = 1;a = 2; 寫一個變量以後,再寫這個變量。
讀後寫 a = b;b = 1; 讀一個變量以後,再寫這個變量。

上面三種狀況,只要重排序兩個操做的執行順序,程序的執行結果將會被改變。前面提到過,編譯器和處理器可能會對操做作重排序。編譯器和處理器在重排序時,會遵照數據依賴性,編譯器和處理器不會改變存在數據依賴關係的兩個操做的執行順序。注意,這裏所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操做,不一樣處理器之間和不一樣線程之間的數據依賴性不被編譯器和處理器考慮。若是兩個操做訪問同一個變量,且這兩個操做中有一個爲寫操做,此時這兩個操做之間就存在數據依賴性。因此有數據依賴性的語句不能進行重排序。

原文連接:

https://blog.csdn.net/ThinkWon/article/details/102073858

本文分享自微信公衆號 - 源代碼社區(ydmsq666)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索