作業系統篇-cpu

lovelife110發表於2020-10-13

以下針對java說明

組合語言(機器語言)的執行過程

計算機通電 -> CPU讀取記憶體中程式(電訊號輸入)->時鐘發生器不斷震盪通斷電 ->推動CPU內部一步一步執行(執行多少步取決於指令需要的時鐘週期)->計算完成->寫回(電訊號)->寫給顯示卡輸出(sout,或者圖形)

cpu為什麼需要時鐘發生器:
時鐘是為了同步CPU中各種閘電路。

超執行緒

在這裡插入圖片描述
四核八執行緒 其實就是一個ALU對應2個暫存器(registers),平時在讀取資料的時候,就會把一個執行緒相關的資料儲存在暫存器裡,指令地址儲存在PC裡面,之後ALU對資料進行計算,如果CPU時間片到了切換執行緒的時候,就可以把暫存器直接切換到下一個暫存器裡進行下一個資料的運算。而不用像只有一個暫存器的時候,在切換執行緒的時候,需要先把當前暫存器的資料存回記憶體。

CPU快取的結構

下圖2個cpu,1個cpu2核
在這裡插入圖片描述
L3有的也有在主機板上,離cpu很近

快取行

在讀取資料的時候是按塊讀取,這些塊,在快取的領域被稱作快取行
程式區域性性原理,可以提高效率
充分發揮匯流排CPU針腳等一次性讀取更多資料的能力

快取行越大,區域性性空間效率越高,但讀取時間慢
快取行越小,區域性性空間效率越低,但讀取時間快
取一個折中值,目前多用:64位元組

快取一致性協議

讀取資料會先把資料讀到快取行,如果有一個資料同時被兩個執行緒訪問,其中一個cpu修改了資料,這個時候會觸發MESI Cache資料一致性協議(intel cpu),會使另一個執行緒的快取行失效,另一個資料會從新從記憶體中讀取新的資料。
簡單的講,就是給快取行標記四種狀態,分別是Modified(被修改)、Exclusive(獨享的)、Shared(共享的)、Invalid(失效的)。根據不同的狀態進行操作達成資料的一致性。
但是有的資料,一個快取行無法裝下,這個時候如果要保持資料一致性,需要鎖匯流排。
跨多個快取行的資料必須使用匯流排鎖
在這裡插入圖片描述

快取行對齊

快取行對齊:對於有些特別敏感的數字,會存線上程高競爭的訪問,為了保證不發生偽共享(多個執行緒同時讀取到了同一快取行時,為了使執行緒之間的可見性,會使用volatile關鍵字,使其他執行緒的快取行失效進而從記憶體中從新讀取資料,進而造成效能的降低),可以使用快取行對齊的程式設計方式

JDK7中,很多采用long padding提高效率

JDK8,加入了@Contended註解,需要加上:JVM -XX:-RestrictContended

開源專案例子:disruptor 高效能併發佇列
java 一個long 8個位元組,p1~p7加上插入的一個p,就是64位元組,對應一個快取行
在這裡插入圖片描述

亂序執行

CPU在進行讀等待的同時執行其他指令,是CPU亂序的根源,不是亂,而是提高效率。

亂序執行可能會產生的問題

經典例子:DCL(Double Check Lock)單例為什麼要加volative?

public class Singleton {
	// 加volatile禁止指令重排序
    private volatile Singleton singleton;
    // 建構函式是private,防止外部例項化
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) { // 第一次檢查
            synchronized (Singleton.class) {
                if (singleton == null) { // 第二次檢查,"double check"的由來
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

在這裡插入圖片描述
new指令不是原子:
new的時候只是在記憶體申請一個空間並在棧頂壓入了指向這段記憶體的地址(即引用),只有執行完invokespecial後才把m賦值為8。
dup,即 duplicate,其作用就是複製之前分配的new 空間的引用並壓入棧頂,為什麼需要這個是因為invokespecial 通過#3這個常量池入口找到構造方法,需要知道是誰的構造方法,所以需要消耗一個引用才能執行構造方法,然後彈出棧。
astore_1:呼叫astore_1將此時的棧頂值彈出存入區域性變數中第1的位置(第0位置是this)。

所以,在new一半的時候,可能會發生指令重排,所以DCL(Double Check Lock)單例要加volative
在這裡插入圖片描述

CPU層面如何禁止亂序?

記憶體屏障
對某部分記憶體做操作時前後新增屏障,屏障前後的操作時不可以亂序執行的。
在這裡插入圖片描述
Intel cpu
底層實現:原語,彙編指令(mfence lfence sfence) 或者匯流排鎖

sfence,寫屏障,在sfence指令前的寫操作當必須在sfence指令後寫操作前完成。
lfence,讀屏障,在lfence指令前的讀操作當必須在lfence指令後的讀操作之前完成。
mfence,讀寫屏障,在mfence執行之前的讀寫操作必須在mfence指令後的讀寫操作之前完成。

intel lock指令
是一個Full Barrier,執行時會鎖住記憶體子系統來確保執行順序,甚至跨多個cpu。

JVM規範禁止亂序

JSR記憶體屏障:
在這裡插入圖片描述
以下是JVM規範volatile實現,但是在hotspot裡,是通過 lock addl $0x0,(%rsp)實現。

StoreStoreBarrier //寫寫屏障
volatile 寫操作
StoreLoadBarrier //寫讀屏障

LoadLoadBarrier
volatile讀操作
LoadStoreBarrier

8個hanppens-before原則:在這裡插入圖片描述

as-if-serial : 不管硬體什麼順序,單執行緒執行的結果不變,看上去像是按順序。
比如在一個單執行緒裡面
x=1
y=2
換為
y=2
x=1
單執行緒執行的結果不變,看上去就像是按順序執行。

相關文章