導航:首頁 > 源碼編譯 > 編譯器重排序和處理器重排序

編譯器重排序和處理器重排序

發布時間:2022-01-23 17:37:05

① 什麼是指令重排

什麼是指令重排序?
有兩個層面:
**在虛擬機層面,**為了盡可能減少內存操作速度遠慢於CPU運行速度所帶來的CPU空置的影響,虛擬機會按照自己的一些規則(這規則後面再敘述)將程序編寫順序打亂——即寫在後面的代碼在時間順序上可能會先執行,而寫在前面的代碼會後執行——以盡可能充分地利用CPU。拿上面的例子來說:假如不是a=1的操作,而是a=new byte1024*1024,那麼它會運行地很慢,此時CPU是等待其執行結束呢,還是先執行下面那句flag=true呢?顯然,先執行flag=true可以提前使用CPU,加快整體效率,當然這樣的前提是不會產生錯誤(什麼樣的錯誤後面再說)。雖然這里有兩種情況:後面的代碼先於前面的代碼開始執行;前面的代碼先開始執行,但當效率較慢的時候,後面的代碼開始執行並先於前面的代碼執行結束。不管誰先開始,總之後面的代碼在一些情況下存在先結束的可能。
**在硬體層面,**CPU會將接收到的一批指令按照其規則重排序,同樣是基於CPU速度比緩存速度快的原因,和上一點的目的類似,只是硬體處理的話,每次只能在接收到的有限指令范圍內重排序,而虛擬機可以在更大層面、更多指令范圍內重排序。硬體的重排序機制參見《從JVM並發看CPU內存指令重排序(Memory Reordering)》
java提供了兩個關鍵字volatile和synchronized來保證多線程之間操作的有序性,volatile關鍵字本身通過加入內存屏障來禁止指令的重排序,而synchronized關鍵字通過一個變數在同一時間只允許有一個線程對其進行加鎖的規則來實現。
在單線程程序中,不會發生「指令重排」和「工作內存和主內存同步延遲」現象,只在多線程程序中出現。
1)編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
2)指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-LevelParallelism,ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
3)內存系統的重排序。由於處理器使用緩存和讀/寫緩沖區,這使得載入和存儲操作看上去可能是在亂序執行

② 請教處理器cache與指令重排序問題

存儲器:具有記憶功能的物理器件,用於存儲信息。存儲器分為內存和外存 ①內存是半導體存儲器(主存): 它分為只讀存儲器(ROM)和隨機存儲器(RAM)和高速緩沖存儲器(Cache); ROM:只能讀,不能用普通方法寫入,通常由廠家生產時寫入,寫入後數據不容易丟失,也可以用特殊方法(如紫外線擦除(EPROM)或電擦除(EEPROM_)存儲器); RAM:可讀可寫,斷電後內容全部丟失; Cache:因為CPU讀寫RAM的時間需要等待,為了減少等待時間,在RAM和CPU間需要設置高速緩存Cache,斷電後其內容丟失。 ②外存:磁性存儲器——軟盤和硬碟;光電存儲器——光碟,它們可以作為永久存器; ③存儲器的兩個重要技術指標:存取速度和存儲容量。內存的存取速度最快(與CPU速 度相匹配),軟盤存取速度最慢。存儲容量是指存儲的信息量,它用位元組(Byte)作為基本單位, 1位元組用8位二進制數表示,1KB=1024B,1MB=1024KB,lGB=1024MB

③ java指令重排序,happens-before的問題

不會的。
java代碼肯定是執行t.i = 1這個後,
再執行new Thread(t).start();這個
所以不會出現你說的情況

④ 指令重排序會破壞happens-before原則嗎

線程A:
readConfig(); //讀取配置
init=true;

線程B:
while(init){
useConfig(); //使用配置
}

由於線程A可能會發生指令重排序,所以線程B使用的配置可能尚未載入,所以使用volatile解決此問題。

比如說,在readConfig();里有N多的指令要執行
指令
a
b
c
d
init=true;

如果abcd和init變數都沒有關系,就是不存在happens-before關系的話,若果被重排,比如說可能變成
a
b
init=true;
c
d

此時其實c,d還沒有執行 但是b線程里init=true;已經成立了。。。所以就執行 useConfig(); 了 然後會出錯

⑤ 幾種常見的數據依賴

如果兩個操作訪問同一個變數,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在數據依賴性。數據依賴分為下列3種類型,如表3-4所示。



上面3種情況,只要重排序兩個操作的執行順序,程序的執行結果就會被改變。

前面提到過,編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序。

這里所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,不同處理器之間和不同線程之間的數據依賴性不被編譯器和處理器考慮。

註:本文源自《Java並發編程的藝術》一文。

⑥ java中volatile修飾的變數有什麼特徵

volatile是一個類型修飾符,它是被設計用來修飾被不同線程訪問和修改的變數,可以被非同步的線程所修改。
final必須對它賦予初值並且不能修改它。
對比就知道兩個修飾符是沖突的,放一起是要干什麼呢?

⑦ SET I1=100 指令的含義是將100的值賦給()

摘要 重排序後, a 的兩次操作被放到一起,指令執行情況變為 Load a、Set to 100、Set to 110、 Store a。下面和 b 相關的指令不變,仍對應 Load b、 Set to 5、Store b。

⑧ 線程安全的關鍵字

Java語言中關鍵字 volatile 被稱作輕量級的 synchronized,與synchronized相比,volatile編碼相對簡單且運行的時的開銷較少,但能夠正確合理的應用好 volatile 並不是那麼的容易,因為它比使用鎖更容易出錯,接下來本文主要介紹 volatile 的使用准則,以及使用過程中需注意的地方。
為何使用volatile?
(1)簡易性:在某些需要同步的場景下使用volatile變數要比使用鎖更加簡單
(2)性能:在某些情況下使用volatile同步機制的性能要優於鎖
(3)volatile操作不會像鎖一樣容易造成阻塞
volatile特性
(1)volatile 變數具有 synchronized 的可見性特性,及如果一個欄位被聲明為volatile,java線程內存模型確保所有的線程看到這個變數的值是一致的
(2)禁止進行指令重排序
(3)不保證原子性
註:① 重排序:重排序通常是編譯器或運行時環境為了優化程序性能而採取的對指令進行重新排序執行的一種手段
② 原子性:不可中斷的一個或一系列操作
③ 可見性:鎖提供了兩種主要特性:互斥和可見性,互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程能夠使用該共享數據。可見性要更加復雜一些,它必須確保釋放鎖之前對共享數據做出的更改對於隨後獲得該鎖的另一個線程是可見的。
volatile的實現原理
如果對聲明了volatile的變數進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,該Lock指令會使這個變數所在緩存行的數據回寫到系統內存,根據緩存一致性協議,每個處理器都會通過嗅探在匯流排上傳輸的數據來檢查自己緩存的值是否已過期,當處理器發現自己的緩存行對應的地址被修改,就會將當前處理器的緩存行設置成無效狀態,在下次訪問相同內存地址時,強制執行緩存行填充。

⑨ java中虛擬機的內存到底分為幾類呢,網上說法挺多,能不能給個專業的

Java內存模型
主內存與工作內存
Java內存模型的主要目標是定義程序中各個變數的訪問規則,即在虛擬機中將變數存儲到內存和從內存中取出變數這樣底層細節。此處的變數與Java編程時所說的變數不一樣,指包括了實例欄位、靜態欄位和構成數組對象的元素,但是不包括局部變數與方法參數,後者是線程私有的,不會被共享。
Java內存模型中規定了所有的變數都存儲在主內存中,每條線程還有自己的工作內存(可以與前面將的處理器的高速緩存類比),線程的工作內存中保存了該線程使用到的變數到主內存副本拷貝,線程對變數的所有操作(讀取、賦值)都必須在工作內存中進行,而不能直接讀寫主內存中的變數。不同線程之間無法直接訪問對方工作內存中的變數,線程間變數值的傳遞均需要在主內存來完成,線程、主內存和工作內存的交互關系如下圖所示

這里的主內存、工作內存與Java內存區域的Java堆、棧、方法區不是同一層次內存劃分。
內存間交互操作
關於主內存與工作內存之間的具體交互協議,即一個變數如何從主內存拷貝到工作內存、如何從工作內存同步到主內存之間的實現細節,Java內存模型定義了以下八種操作來完成:
· lock(鎖定):作用於主內存的變數,把一個變數標識為一條線程獨占狀態。
· unlock(解鎖):作用於主內存變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他線程鎖定。
· read(讀取):作用於主內存變數,把一個變數值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
· load(載入):作用於工作內存的變數,它把read操作從主內存中得到的變數值放入工作內存的變數副本中。
· use(使用):作用於工作內存的變數,把工作內存中的一個變數值傳遞給執行引擎,每當虛擬機遇到一個需要使用變數的值的位元組碼指令時將會執行這個操作。
· assign(賦值):作用於工作內存的變數,它把一個從執行引擎接收到的值賦值給工作內存的變數,每當虛擬機遇到一個給變數賦值的位元組碼指令時執行這個操作。
· store(存儲):作用於工作內存的變數,把工作內存中的一個變數的值傳送到主內存中,以便隨後的write的操作。
· write(寫入):作用於主內存的變數,它把store操作從工作內存中一個變數的值傳送到主內存的變數中。
如果要把一個變數從主內存中復制到工作內存,就需要按順尋地執行read和load操作,如果把變數從工作內存中同步回主內存中,就要按順序地執行store和write操作。Java內存模型只要求上述操作必須按順序執行,而沒有保證必須是連續執行。也就是read和load之間,store和write之間是可以插入其他指令的,如對主內存中的變數a、b進行訪問時,可能的順序是read a,read b,load b, load a。Java內存模型還規定了在執行上述八種基本操作時,必須滿足如下規則:
· 不允許read和load、store和write操作之一單獨出現
· 不允許一個線程丟棄它的最近assign的操作,即變數在工作內存中改變了之後必須同步到主內存中。
· 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。
· 一個新的變數只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變數。即就是對一個變數實施use和store操作之前,必須先執行過了assign和load操作。
· 一個變數在同一時刻只允許一條線程對其進行lock操作,lock和unlock必須成對出現
· 如果對一個變數執行lock操作,將會清空工作內存中此變數的值,在執行引擎使用這個變數前需要重新執行load或assign操作初始化變數的值
· 如果一個變數事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變數。
· 對一個變數執行unlock操作之前,必須先把此變數同步到主內存中(執行store和write操作)。
重排序
在執行程序時為了提高性能,編譯器和處理器經常會對指令進行重排序。重排序分成三種類型:

編譯器優化的重排序。編譯器在不改變單線程程序語義放入前提下,可以重新安排語句的執行順序。

指令級並行的重排序。現代處理器採用了指令級並行技術來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。

內存系統的重排序。由於處理器使用緩存和讀寫緩沖區,這使得載入和存儲操作看上去可能是在亂序執行。
從Java源代碼到最終實際執行的指令序列,會經過下面三種重排序:

為了保證內存的可見性,Java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。Java內存模型把內存屏障分為LoadLoad、LoadStore、StoreLoad和StoreStore四種:

同步機制
介紹volatile、synchronized和final
原子性、可見性與有序性
Java內存模型JMM解決了可見性和有序性的問題,而鎖解決了原子性的問題。

可見性
指的是一個線程對變數的寫操作對其他線程後續的讀操作可見。由於現代CPU都有多級緩存,CPU的操作都是基於高速緩存的,而線程通信是基於內存的,這中間有一個Gap,可見性的關鍵還是在對變數的寫操作之後能夠在某個時間點顯示地寫回到主內存,這樣其他線程就能從主內存中看到最新的寫的值。volatile,synchronized(隱式鎖), 顯式鎖,原子變數這些同步手段都可以保證可見性。
可見性底層的實現是通過加內存屏障實現的:
1. 寫變數後加寫屏障,保證CPU寫緩沖區的值強制刷新回主內存
2. 讀變數之前加讀屏障,使緩存失效,從而強制從主內存讀取變數最新值
寫volatile變數 = 進入鎖
讀volatile變數 = 釋放鎖

有序性
指的是數據不相關的變數在並發的情況下,實際執行的結果和單線程的執行結果是一樣的,不會因為重排序的問題導致結果不可預知。volatile, final, synchronized,顯式鎖都可以保證有序性。
有序性的語意有幾層,
1. 最常見的就是保證多線程執行的串列順序
2. 防止重排序引起的問題
3. 程序執行的先後順序,比如JMM定義的一些Happens-before規則

重排序
的問題是一個單獨的主題,常見的重排序有3個層面:
1. 編譯級別的重排序,比如編譯器的優化
2. 指令級重排序,比如CPU指令執行的重排序
3. 內存系統的重排序,比如緩存和讀寫緩沖區導致的重排序

原子性
是指某個(些)操作在語意上是原子的。比如讀操作,寫操作,CAS(compareand set)操作在機器指令級別是原子的,又比如一些復合操作在語義上也是原子的,如先檢查後操作if(xxx== null){}
有個專有名詞競態條件來描述原子性的問題。
競態條件(racing condition)是指某個操作由於不同的執行時序而出現不同的結果,比如先檢查後操作。
volatile變數只保證了可見性,不保證原子性,比如a++這種操作在編譯後實際是多條語句,比如先讀a的值,再加1操作,再寫操作,執行了3個原子操作,如果並發情況下,另外一個線程很有可能讀到了中間狀態,從而導致程序語意上的不正確。所以a++實際是一個復合操作。
加鎖可以保證復合語句的原子性,sychronized可以保證多條語句在synchronized塊中語意上是原子的。
顯式鎖保證臨界區的原子性。
原子變數也封裝了對變數的原子操作。
非阻塞容器也提供了原子操作的介面,比如putIfAbsent。

⑩ C++編譯器(Dev-C)是否會自動內聯函數 對於什麼樣的函數即使標記inline也會拒絕內聯

G++編譯器是否會自動進行內聯函數?

G++編譯器是很先進的,編譯的時候如果開啟優化,G++會代碼進行各種優化,如:對合適的函數進行內聯(即便是沒有添加inline關鍵字),對某些函數直接對其進行求值,除此之外G++編譯器還可以對代碼進行重排序 等等。編譯器比你更了解硬體,所以只要允許它優化,他會盡量進行優化。你使用的Dev C++集成開發環境使用的c++編譯器就是G++。


什麼樣的函數即使標記inline也無法內聯?

比如函數體太大、太復雜的話(比如包含多重循環、包含遞歸調用),對其進行內聯得不償失,這時編譯器就會忽略inline關鍵字,VC++編譯器提供了強制內聯函數的關鍵字,除非你非常了解硬體,不然最好讓編譯器來處。編譯不對那些函數進行內聯要看具體的編譯器實現了。


inline關鍵字的有哪些作用?

inline關鍵字可以提示編譯器對某個函數進行內聯,並且強制函數使用內部鏈接。比如說你在頭文件定義了某個函數,為了防止多重定義,你可以添加inline關鍵字來防止多重定義錯誤。


如果對硬體不是很了解,底層的代碼優化還是留給編譯器來處理。


看看下面的幾個編譯器優化函數的例子:


1.編譯器直接對函數求值:

解釋一下:

第一條和第二天指令分別將b和a的地址載入到寄存器rdx和rcx中

第三條指令將b的值載入到eax寄存器中

第四條指令將34存入b中

第五條指令將eax的值加1(eax保存了之前b的值)

第六條指令將eax的值存入a中

可以看出編譯器將函數的兩條語句換了位置,這種優化主要是優化代碼的執行速度,有的CPU內存讀寫操作的的開銷不一樣,所以重新排序一下某些代碼能夠提高程序執行速度。

閱讀全文

與編譯器重排序和處理器重排序相關的資料

熱點內容
安卓手機為什麼總是出現藍屏 瀏覽:252
u盤超級加密3000加密後 瀏覽:877
sql插入數據命令 瀏覽:470
u盤根目錄文件夾是哪個 瀏覽:693
新預演算法預算編制 瀏覽:622
perl怎樣遍歷文件夾 瀏覽:636
安卓手機如何更好的保護隱私 瀏覽:316
程序員書籍知乎 瀏覽:154
王者安卓v區怎麼轉移到蘋果 瀏覽:449
加密區卸載 瀏覽:122
女程序員壓力大想辭職 瀏覽:681
演算法體現在哪裡 瀏覽:219
阿里雲個人伺服器推薦 瀏覽:363
汽車識別視頻文件夾 瀏覽:110
檔案伺服器不可用是什麼意思 瀏覽:525
有什麼app能看到老婆在哪 瀏覽:562
androidpdf源碼 瀏覽:435
方舟怎麼把單機人物上傳到伺服器 瀏覽:964
偏置命令下大小形狀保持不變 瀏覽:988
單片機各功能介面晶元 瀏覽:795