一、CPU指令的執(zhí)行過(guò)程
幾乎所有的馮·諾伊曼型計(jì)算機(jī)的 CPU,其工作都可以分為 5 個(gè)階段:取指令、指令譯碼、執(zhí)行指令、訪存取數(shù)、結(jié)果寫(xiě)回。
圖1 CPU指令的執(zhí)行階段
?
1.取指令階段
取指令(Instruction Fetch,IF)階段是將一條指令從主存中取到指令寄存器的過(guò)程。 程序計(jì)數(shù)器 PC 中的數(shù)值,用來(lái)指示當(dāng)前指令在主存中的位置。當(dāng)一條指令被取出后,PC 中的數(shù)值將根據(jù)指令字長(zhǎng)度而自動(dòng)遞增:若為單字長(zhǎng)指令,則(PC)+1->PC;若為雙字長(zhǎng)指令,則(PC)+2->PC,依此類推。
2.指令譯碼階段
取出指令后,計(jì)算機(jī)立即進(jìn)入指令譯碼(Instruction Decode,ID)階段。 在指令譯碼階段,指令譯碼器按照預(yù)定的指令格式,對(duì)取回的指令進(jìn)行拆分和解釋,識(shí)別區(qū)分出不同的指令類別以及各種獲取操作數(shù)的方法。在組合邏輯控制的計(jì)算機(jī)中,指令譯碼器對(duì)不同的指令操作碼產(chǎn)生不同的控制電位,以形成不同的微操作序列;在微程序控制的計(jì)算機(jī)中,指令譯碼器用指令操作碼來(lái)找到執(zhí)行該指令的微程序的入口,并從此入口開(kāi)始執(zhí)行。 在傳統(tǒng)的設(shè)計(jì)里,CPU中負(fù)責(zé)指令譯碼的部分是無(wú)法改變的。不過(guò),在眾多運(yùn)用微程序控制技術(shù)的新型 CPU 中,微程序有時(shí)是可重寫(xiě)的。
3.執(zhí)行指令階段
在取指令和指令譯碼階段之后,接著進(jìn)入執(zhí)行指令(Execute,EX)階段。 此階段的任務(wù)是完成指令所規(guī)定的各種操作,具體實(shí)現(xiàn)指令的功能。為此,CPU 的不同部分被連接起來(lái),以執(zhí)行所需的操作。 例如,如果要求完成一個(gè)加法運(yùn)算,算術(shù)邏輯單元 ALU 將被連接到一組輸入和一組輸出,輸入端提供需要相加的數(shù)值,輸出端將含有最后的運(yùn)算結(jié)果。
4.訪存取數(shù)階段
根據(jù)指令需要,有可能要訪問(wèn)主存,讀取操作數(shù),這樣就進(jìn)入了訪存取數(shù)(Memory,MEM)階段。 此階段的任務(wù)是:根據(jù)指令地址碼,得到操作數(shù)在主存中的地址,并從主存中讀取該操作數(shù)用于運(yùn)算。
5.結(jié)果寫(xiě)回階段
作為最后一個(gè)階段,結(jié)果寫(xiě)回(Writeback,WB)階段把執(zhí)行指令階段的運(yùn)行結(jié)果數(shù)據(jù)“寫(xiě)回”到某種存儲(chǔ)形式:結(jié)果數(shù)據(jù)經(jīng)常被寫(xiě)到 CPU 的內(nèi)部寄存器中,以便被后續(xù)的指令快速地存??;在有些情況下, 結(jié)果數(shù)據(jù)也可被寫(xiě)入相對(duì)較慢、但較廉價(jià)且容量較大的主存。許多指令還會(huì)改變程序狀態(tài)字寄存器中標(biāo)志位 的狀態(tài),這些標(biāo)志位標(biāo)識(shí)著不同的操作結(jié)果,可被用來(lái)影響程序的動(dòng)作。
在指令執(zhí)行完畢、結(jié)果數(shù)據(jù)寫(xiě)回之后,若無(wú)意外事件(如結(jié)果溢出等)發(fā)生,計(jì)算機(jī)就接著從程序計(jì)數(shù)器 PC 中取得下一條指令地址,開(kāi)始新一輪的循環(huán),下一個(gè)指令周期將順序取出下一條指令。許多新型 CPU 可以同時(shí)取出、譯碼和執(zhí)行多條指令,體現(xiàn)并行處理的特性。
二、CPU指令流水線
在任一條指令的執(zhí)行過(guò)程中,各個(gè)功能部件都會(huì)隨著指令執(zhí)行的進(jìn)程而呈現(xiàn)出時(shí)忙時(shí)閑的現(xiàn)象。要加快計(jì)算機(jī)的工作速度,就應(yīng)使各個(gè)功能部件并行工作,即以各自可能的高速度同時(shí)、不停地工作,使得各部件的操作在時(shí)間上重疊進(jìn)行,實(shí)現(xiàn)流水式作業(yè)。 從原理上說(shuō),計(jì)算機(jī)的流水線(Pipeline)工作方式就是將一個(gè)計(jì)算任務(wù)細(xì)分成若干個(gè)子任務(wù),每個(gè)子任務(wù)都由專門的功能部件進(jìn)行處理,一個(gè)計(jì)算任務(wù)的各個(gè)子任務(wù)由流水線上各個(gè)功能部件輪流進(jìn)行處理 (即各子任務(wù)在流水線的各個(gè)功能階段并發(fā)執(zhí)行),最終完成工作。這樣,不必等到上一個(gè)計(jì)算任務(wù)完成, 就可以開(kāi)始下一個(gè)計(jì)算任務(wù)的執(zhí)行。 流水線的硬件基本結(jié)構(gòu)如圖2所示。流水線由一系列串聯(lián)的功能部件(Si)組成,各個(gè)功能部件之間設(shè)有高速緩沖寄存器(L),以暫時(shí)保存上一功能部件對(duì)子任務(wù)處理的結(jié)果,同時(shí)又能夠接受新的處理任務(wù)。在一個(gè)統(tǒng)一的時(shí)鐘(C)控制下,計(jì)算任務(wù)從功能部件的一個(gè)功能段流向下一個(gè)功能段。在流水線中, 所有功能段同時(shí)對(duì)不同的數(shù)據(jù)進(jìn)行不同的處理,各個(gè)處理步驟并行地操作。
圖2 流水線的硬件基本結(jié)構(gòu)?
?
當(dāng)任務(wù)連續(xù)不斷地輸入流水線時(shí),在流水線的輸出端便連續(xù)不斷地輸出執(zhí)行結(jié)果,流水線達(dá)到不間斷流水的穩(wěn)定狀態(tài),從而實(shí)現(xiàn)了子任務(wù)級(jí)的并行。
當(dāng)指令流不能順序執(zhí)行時(shí),流水過(guò)程會(huì)中斷(即斷流)。為了保證流水過(guò)程的工作效率,流水過(guò)程不應(yīng)經(jīng)常斷流。在一個(gè)流水過(guò)程中,實(shí)現(xiàn)各個(gè)子過(guò)程的各個(gè)功能段所需要的時(shí)間應(yīng)該盡可能保持相等,以避免產(chǎn)生瓶頸,導(dǎo)致流水線斷流。
流水線技術(shù)本質(zhì)上是將一個(gè)重復(fù)的時(shí)序過(guò)程分解成若干個(gè)子過(guò)程,而每一個(gè)子過(guò)程都可有效地在其專用功能段上與其他子過(guò)程同時(shí)執(zhí)行。采用流水線技術(shù)通過(guò)硬件實(shí)現(xiàn)并行操作后,就某一條指令而言,其執(zhí)行速度并沒(méi)有加快,但就程序執(zhí)行過(guò)程的整體而言,程序執(zhí)行速度大大加快。
流水線技術(shù)適合于大量的重復(fù)性的處理。
前面我提到過(guò)CPU 中一個(gè)指令周期的任務(wù)分解。假設(shè)指令周期包含取指令(IF)、指令譯碼(ID)、 指令執(zhí)行(EX)、訪存取數(shù)(MEM)、結(jié)果寫(xiě)回(WB)5 個(gè)子過(guò)程(過(guò)程段),流水線由這 5個(gè)串聯(lián)的過(guò)程段 組成,各個(gè)過(guò)程段之間設(shè)有高速緩沖寄存器,以暫時(shí)保存上一過(guò)程段子任務(wù)處理的結(jié)果,在統(tǒng)一的時(shí)鐘信號(hào)控制下,數(shù)據(jù)從一個(gè)過(guò)程段流向相鄰的過(guò)程段。
非流水計(jì)算機(jī)的時(shí)空?qǐng)D如下:
?圖3 非流水計(jì)算機(jī)時(shí)空?qǐng)D
?
對(duì)于非流水計(jì)算機(jī)而言,上一條指令的 5 個(gè)子過(guò)程全部執(zhí)行完畢后才能開(kāi)始下一條指令,每隔 5 個(gè)時(shí) 鐘周期才有一個(gè)輸出結(jié)果。因此,圖3中用了 15 個(gè)時(shí)鐘周期才完成 3 條指令,每條指令平均用時(shí) 5 個(gè)時(shí)鐘周期。 非流水線工作方式的控制比較簡(jiǎn)單,但部件的利用率較低,系統(tǒng)工作速度較慢。
標(biāo)量流水計(jì)算機(jī)工作方式
標(biāo)量(Scalar)流水計(jì)算機(jī)是只有一條指令流水線的計(jì)算機(jī)。圖 4表示標(biāo)量流水計(jì)算機(jī)的時(shí)空?qǐng)D。
?圖4 標(biāo)量流水計(jì)算機(jī)時(shí)空?qǐng)D
?
對(duì)標(biāo)量流水計(jì)算機(jī)而言,上一條指令與下一條指令的 5 個(gè)子過(guò)程在時(shí)間上可以重疊執(zhí)行,當(dāng)流水線滿 載時(shí),每一個(gè)時(shí)鐘周期就可以輸出一個(gè)結(jié)果。因此,圖4中僅用了 9 個(gè)時(shí)鐘周期就完成了 5 條指令,每條指令平均用時(shí) 1.8 個(gè)時(shí)鐘周期。
采用標(biāo)量流水線工作方式,雖然每條指令的執(zhí)行時(shí)間并未縮短,但 CPU 運(yùn)行指令的總體速度卻能成倍 提高。當(dāng)然,作為速度提高的代價(jià),需要增加部分硬件才能實(shí)現(xiàn)標(biāo)量流水。
超標(biāo)量流水計(jì)算機(jī)工作方式
一般的流水計(jì)算機(jī)因只有一條指令流水線,所以稱為標(biāo)量流水計(jì)算機(jī)。所謂超標(biāo)量(Superscalar)流 水計(jì)算機(jī),是指它具有兩條以上的指令流水線。圖 5表示超標(biāo)量流水計(jì)算機(jī)的時(shí)空?qǐng)D。
圖5 超標(biāo)量流水計(jì)算機(jī)時(shí)空?qǐng)D?
?
當(dāng)流水線滿載時(shí),每一個(gè)時(shí)鐘周期可以執(zhí)行 2 條以上的指令。因此,圖5中僅用了 9 個(gè)時(shí)鐘周期就完成了 10 條指令,每條指令平均用時(shí) 0.9 個(gè)時(shí)鐘周期。 超標(biāo)量流水計(jì)算機(jī)是時(shí)間并行技術(shù)和空間并行技術(shù)的綜合應(yīng)用。
三、指令的相關(guān)性
指令流水線的一個(gè)特點(diǎn)是流水線中的各條指令之間存在一些相關(guān)性,使得指令的執(zhí)行受到影響。要使流水線發(fā)揮高效率,就要使流水線連續(xù)不斷地流動(dòng),盡量不出現(xiàn)斷流情況。然而,由于流水過(guò)程中存在的相關(guān)性沖突,斷流現(xiàn)象是不可避免的。
1.?dāng)?shù)據(jù)相關(guān)
在流水計(jì)算機(jī)中,指令的處理是重疊進(jìn)行的,前一條指令還沒(méi)有結(jié)束,第二、三條指令就陸續(xù)開(kāi)始工 作。由于多條指令的重疊處理,當(dāng)后繼指令所需的操作數(shù)剛好是前一指令的運(yùn)算結(jié)果時(shí),便發(fā)生數(shù)據(jù)相關(guān)沖突。由于這兩條指令的執(zhí)行順序直接影響到操作數(shù)讀取的內(nèi)容,必須等前一條指令執(zhí)行完畢后才能執(zhí)行后一條指令。在這種情況下,這兩條指令就是數(shù)據(jù)相關(guān)的。因此,數(shù)據(jù)相關(guān)是由于指令之間存在數(shù)據(jù)依賴性而引起的。根據(jù)指令間對(duì)同一寄存器讀和寫(xiě)操作的先后次序關(guān)系,可將數(shù)據(jù)相關(guān)性分為寫(xiě)后讀(Read-AfterWrite,RAW)相關(guān)、讀后寫(xiě)(Write-After-Read,WAR)相關(guān)、寫(xiě)后寫(xiě)(Write-After-Write,WAW)相關(guān)三種類型。
解決數(shù)據(jù)相關(guān)沖突的辦法如下:
采用編譯的方法 編譯程序通過(guò)在兩條相關(guān)指令之間插入其他不相關(guān)的指令(或空操作指令)而推遲指令的執(zhí)行,使數(shù)據(jù)相關(guān)消失,從而產(chǎn)生沒(méi)有相關(guān)性的程序代碼。這種方式簡(jiǎn)單,但降低了運(yùn)行效率。
由硬件監(jiān)測(cè)相關(guān)性的存在,采用數(shù)據(jù)旁路技術(shù)設(shè)法解決數(shù)據(jù)相關(guān) 當(dāng)前一條指令要寫(xiě)入寄存器而下一條指令要讀取同一個(gè)寄存器時(shí),在前一條指令執(zhí)行完畢、結(jié)果數(shù)據(jù)還未寫(xiě)入寄存器前,由內(nèi)部數(shù)據(jù)通路把該結(jié)果數(shù)據(jù)直接傳遞給下一條指令,也就是說(shuō),下一條指令所需的 操作數(shù)不再通過(guò)讀取寄存器獲得,而是直接獲取。這種方式效率較高,但控制較為復(fù)雜。
2.資源相關(guān)
所謂資源相關(guān),是指多條指令進(jìn)入流水線后在同一機(jī)器周期內(nèi)爭(zhēng)用同一個(gè)功能部件所發(fā)生的沖突。 例如,在圖 4所示的標(biāo)量流水計(jì)算機(jī)中,在第 4 個(gè)時(shí)鐘周期時(shí),第 1 條指令處于訪存取數(shù)(MEM) 階段,而第 4 條指令處于取指令(IF)階段。如果數(shù)據(jù)和指令存放在同一存儲(chǔ)器中,且存儲(chǔ)器只有一個(gè)端口,這樣便會(huì)發(fā)生這兩條指令爭(zhēng)用存儲(chǔ)器的資源相關(guān)沖突。 因?yàn)槊恳粭l指令都可能需要 2 次訪問(wèn)存儲(chǔ)器(讀指令和讀寫(xiě)數(shù)據(jù)),在指令流水過(guò)程中,可能會(huì)有 2 條指令同時(shí)需要訪問(wèn)存儲(chǔ)器,導(dǎo)致資源相關(guān)沖突解決資源相關(guān)沖突的一般辦法是增加資源,例如增設(shè)一個(gè)存儲(chǔ)器,將指令和數(shù)據(jù)分別放在兩個(gè)存儲(chǔ)器中。
3.控制相關(guān)
控制相關(guān)沖突是由轉(zhuǎn)移指令引起的。當(dāng)執(zhí)行轉(zhuǎn)移指令時(shí),依據(jù)轉(zhuǎn)移條件的產(chǎn)生結(jié)果,可能順序取下一 條指令,也可能轉(zhuǎn)移到新的目標(biāo)地址取指令。若轉(zhuǎn)移到新的目標(biāo)地址取指令,則指令流水線將被排空,并等待轉(zhuǎn)移指令形成下一條指令的地址,以便讀取新的指令,這就使得流水線發(fā)生斷流。 為了減小轉(zhuǎn)移指令對(duì)流水線性能的影響,通常采用以下兩種轉(zhuǎn)移處理技術(shù):
延遲轉(zhuǎn)移法 由編譯程序重排指令序列來(lái)實(shí)現(xiàn)。其基本思想是“先執(zhí)行再轉(zhuǎn)移”,即發(fā)生轉(zhuǎn)移時(shí)并不排空指令流水線,而是繼續(xù)完成下幾條指令。如果這些后繼指令是與該轉(zhuǎn)移指令結(jié)果無(wú)關(guān)的有用指令,那么延遲損失時(shí)間片正好得到了有效的利用。
轉(zhuǎn)移預(yù)測(cè)法 用硬件方法來(lái)實(shí)現(xiàn)。依據(jù)指令過(guò)去的行為來(lái)預(yù)測(cè)將來(lái)的行為,即選擇出現(xiàn)概率較高的分支進(jìn)行預(yù)取。通過(guò)使用轉(zhuǎn)移取和順序取兩路指令預(yù)取隊(duì)列以及目標(biāo)指令 Cache,可將轉(zhuǎn)移預(yù)測(cè)提前到取指令階段進(jìn)行,以獲得良好的效果。
四、指令的動(dòng)態(tài)執(zhí)行技術(shù)
1.指令調(diào)度
為了減少指令相關(guān)性對(duì)執(zhí)行速度的影響,可以在保證程序正確性的前提下,調(diào)整指令的順序,即進(jìn)行指令調(diào)度。 指令調(diào)度可以由編譯程序進(jìn)行,也可以由硬件在執(zhí)行的時(shí)候進(jìn)行,分別稱為靜態(tài)指令調(diào)度和動(dòng)態(tài)指令調(diào)度。靜態(tài)指令調(diào)度是指編譯程序通過(guò)調(diào)整指令的順序來(lái)減少流水線的停頓,提高程序的執(zhí)行速度;動(dòng)態(tài) 指令調(diào)度用硬件方法調(diào)度指令的執(zhí)行以減少流水線停頓。
流水線中一直采用的有序(in-order)指令啟動(dòng)是限制流水線性能的主要因素之一。如果有一條指令在流水線中停頓了,則其后的指令就都不能向前流動(dòng)了,這樣,如果相鄰的兩條指令存在相關(guān)性,流水線就將發(fā)生停頓,如果有多個(gè)功能部件,這些部件就有可能被閑置。消除這種限制流水線性能的因素從而提高指令執(zhí)行速度,其基本思想就是允許指令的執(zhí)行是無(wú)序的(out-of-order,也稱亂序),也就是說(shuō),在保持指令間、數(shù)據(jù)間的依賴關(guān)系的前提下,允許不相關(guān)的指令的執(zhí)行順序與程序的原有順序有所不同,這一思想是實(shí)行動(dòng)態(tài)指令調(diào)度的前提。
2.亂序執(zhí)行技術(shù)
亂序執(zhí)行(Out-of-order Execution)是以亂序方式執(zhí)行指令,即 CPU 允許將多條指令不按程序規(guī)定的順序而分開(kāi)發(fā)送給各相應(yīng)電路單元進(jìn)行處理。這樣,根據(jù)各個(gè)電路單元的狀態(tài)和各指令能否提前執(zhí)行的具體情況分析,將能夠提前執(zhí)行的指令立即發(fā)送給相應(yīng)電路單元予以執(zhí)行,在這期間不按規(guī)定順序執(zhí)行指令;然后由重新排列單元將各執(zhí)行單元結(jié)果按指令順序重新排列。亂序執(zhí)行的目的,就是為了使 CPU 內(nèi)部電路滿負(fù)荷運(yùn)轉(zhuǎn),并相應(yīng)提高 CPU 運(yùn)行程序的速度。
實(shí)現(xiàn)亂序執(zhí)行的關(guān)鍵在于取消傳統(tǒng)的“取指”和“執(zhí)行”兩個(gè)階段之間指令需要線性排列的限制,而使用一個(gè)指令緩沖池來(lái)開(kāi)辟一個(gè)較長(zhǎng)的指令窗口,允許執(zhí)行單元在一個(gè)較大的范圍內(nèi)調(diào)遣和執(zhí)行已譯碼的程序指令流。
3.分支預(yù)測(cè)
分支預(yù)測(cè)(Branch Prediction)是對(duì)程序的流程進(jìn)行預(yù)測(cè),然后讀取其中一個(gè)分支的指令。采用分支預(yù)測(cè)的主要目的是為了提高 CPU的運(yùn)算速度。 分支預(yù)測(cè)的方法有靜態(tài)預(yù)測(cè)和動(dòng)態(tài)預(yù)測(cè)兩類:靜態(tài)預(yù)測(cè)方法比較簡(jiǎn)單,如預(yù)測(cè)永遠(yuǎn)不轉(zhuǎn)移、預(yù)測(cè)永遠(yuǎn)轉(zhuǎn)移、預(yù)測(cè)后向轉(zhuǎn)移等等,它并不根據(jù)執(zhí)行時(shí)的條件和歷史信息來(lái)進(jìn)行預(yù)測(cè),因此預(yù)測(cè)的準(zhǔn)確性不可能很高;動(dòng)態(tài)預(yù)測(cè)方法則根據(jù)同一條轉(zhuǎn)移指令過(guò)去的轉(zhuǎn)移情況來(lái)預(yù)測(cè)未來(lái)的轉(zhuǎn)移情況。 由于程序中的條件分支是根據(jù)程序指令在流水線處理后的結(jié)果來(lái)執(zhí)行的,所以當(dāng) CPU 等待指令結(jié)果時(shí), 流水線的前級(jí)電路也處于等待分支指令的空閑狀態(tài),這樣必然出現(xiàn)時(shí)鐘周期的浪費(fèi)。如果 CPU 能在前條指令結(jié)果出來(lái)之前就預(yù)測(cè)到分支是否轉(zhuǎn)移,那么就可以提前執(zhí)行相應(yīng)的指令,這樣就避免了流水線的空閑等待,也就相應(yīng)提高了 CPU 的運(yùn)算速度。但另一方面,一旦前條指令結(jié)果出來(lái)后證明分支預(yù)測(cè)是錯(cuò)誤的,那么就必須將已經(jīng)裝入流水線執(zhí)行的指令和結(jié)果全部清除,然后再裝入正確的指令重新處理,這樣就比不進(jìn)行分支預(yù)測(cè)而是等待結(jié)果再執(zhí)行新指令還要慢了。
因此,分支預(yù)測(cè)的錯(cuò)誤并不會(huì)導(dǎo)致結(jié)果的錯(cuò)誤,而只是導(dǎo)致流水線的停頓,如果能夠保持較高的預(yù)測(cè) 準(zhǔn)確率,分支預(yù)測(cè)就能提高流水線的性能。
五、實(shí)例分析
前面的知識(shí)只是一個(gè)理論基礎(chǔ)鋪墊,下面我們就結(jié)合一款真實(shí)的CPU架構(gòu)進(jìn)行對(duì)應(yīng)分析,圖6和圖7分別是x86和ARM體系結(jié)構(gòu)的內(nèi)核架構(gòu)圖(都是具有OoOE特性的CPU架構(gòu)),可以看到他們基本的組成都是一樣的(雖然x86是CISC而ARM是RISC,但是現(xiàn)代x86內(nèi)部也是先把CISC翻譯成RISC的),因此我在這里就只分析x86結(jié)構(gòu)。
圖6 intel Nehalem內(nèi)核架構(gòu)圖?
?
圖7 ARM Cortex-A57內(nèi)核架構(gòu)圖?
?
1.取指令階段(IF)
處理器在執(zhí)行指令之前,必須先裝載指令。指令會(huì)先保存在 L1 緩存的 I-cache (Instruction-cache)指令緩存當(dāng)中,Nehalem 的指令拾取單元使用 128bit 帶寬的通道從 I-cache 中讀取指令。這個(gè) I-cache 的大小為 32KB,采用了 4 路組相連,在后面的存取單元介紹中我們可以得知這種比 Core 更少的集合關(guān)聯(lián)數(shù)量是為了降低延遲。
為了適應(yīng)超線程技術(shù),RIP(Relative Instruction Point,相對(duì)指令指針)的數(shù)量也從一個(gè)增加到了兩個(gè),每個(gè)線程單獨(dú)使用一個(gè)。
?
指令拾取單元包含了分支預(yù)測(cè)器(Branch Predictor),分支預(yù)測(cè)是在 Pentium Pro 處理器開(kāi)始加入的功能,預(yù)測(cè)如 if then 這樣的語(yǔ)句的將來(lái)走向,提前讀取相關(guān)的指令并執(zhí)行的技術(shù),可以明顯地提升性能。指令拾取單元也包含了 Hardware Prefetcher,根據(jù)歷史操作預(yù)先加載以后會(huì)用到的指令來(lái)提高性能,這會(huì)在后面得到詳細(xì)的介紹。
?
當(dāng)分支預(yù)測(cè)器決定了走向一個(gè)分支之后,它使用 BTB(Branch Target Buffer,分支目標(biāo)緩沖區(qū))來(lái)保存預(yù)測(cè)指令的地址。Nehalem 從以前的一級(jí) BTB 升級(jí)到了兩個(gè)級(jí)別,這是為了適應(yīng)很大體積的程序(數(shù)據(jù)庫(kù)以及 ERP 等應(yīng)用,跳轉(zhuǎn)分支將會(huì)跨過(guò)很大的區(qū)域并具有很多的分支)。Intel 并沒(méi)有提及 BTB 詳細(xì)的結(jié)構(gòu)。與BTB 相對(duì)的 RSB(Return Stack Buffer,返回堆棧緩沖區(qū))也得到了提升,RSB 用來(lái)保存一個(gè)函數(shù)或功能調(diào)用結(jié)束之后的返回地址,通過(guò)重命名的 RSB 來(lái)避免多次推測(cè)路徑導(dǎo)致的入口/出口破壞。RSB 每個(gè)線程都有一個(gè),一個(gè)核心就擁有兩個(gè),以適應(yīng)超線程技術(shù)的存在。
?
指令拾取單元使用預(yù)測(cè)指令的地址來(lái)拾取指令,它通過(guò)訪問(wèn) L1 ITLB 里的索引來(lái)繼續(xù)訪問(wèn) L1 ICache,128 條目的小頁(yè)面 L1 ITLB 按照兩個(gè)線程靜態(tài)分區(qū),每個(gè)線程可以獲得 64 個(gè)條目,這個(gè)數(shù)目比 Core 2 的少。當(dāng)關(guān)閉超線程時(shí),單獨(dú)的線程將可以獲得全部的 TLB 資 源。除了小頁(yè)面 TLB 之外,Nehalem 還每個(gè)線程擁有 7 個(gè)條目的全關(guān)聯(lián)(Full Associativity) 大頁(yè)面 ITLB,這些 TLB 用于訪問(wèn) 2M/4M 的大容量頁(yè)面,每個(gè)線程獨(dú)立,因此關(guān)閉超線程不會(huì)讓你得到 14 個(gè)大頁(yè)面 ITLB 條目。
?
指令拾取單元通過(guò) 128bit 的總線將指令從 L1 ICache 拾取到一個(gè) 16Bytes(剛好就是 128bit)的預(yù)解碼拾取緩沖區(qū)。128 位的帶寬讓人有些迷惑不解,Opteron 一早就已經(jīng)使用 了 256bit 的指令拾取帶寬。最重要的是,L1D 和 L1I 都是通過(guò) 256bit 的帶寬連接到 L2 Cache 的。
由于一般的CISC x86指令都小于4Bytes(32位x86指令;x86指令的特點(diǎn)就是不等長(zhǎng)), 因此一次可以拾取 4 條以上的指令,而預(yù)解碼拾取緩沖區(qū)的輸出帶寬是 6 指令每時(shí)鐘周期, 因此可以看出指令拾取帶寬確實(shí)有些不協(xié)調(diào),特別是考慮到 64 位應(yīng)用下指令會(huì)長(zhǎng)一些的情 況下(解碼器的輸入輸出能力是 4 指令每時(shí)鐘周期,因此 32 位下問(wèn)題不大)。
指令拾取結(jié)束后會(huì)送到 18 個(gè)條目的指令隊(duì)列,在 Core 架構(gòu),送到的是 LSD 循環(huán)流緩沖區(qū),在后面可以看到,Nehalem 通過(guò)將 LSD 移動(dòng)后更靠后的位置來(lái)提高性能。
2.指令譯碼階段(ID)
在將指令充填到可容納 18 條目的指令隊(duì)列之后,就可以進(jìn)行解碼工作了。解碼是類 RISC (精簡(jiǎn)指令集或簡(jiǎn)單指令集)處理器導(dǎo)致的一項(xiàng)設(shè)計(jì),從 Pentium Pro 開(kāi)始在 IA 架構(gòu)出現(xiàn)。 處理器接受的是 x86 指令(CISC 指令,復(fù)雜指令集),而在執(zhí)行引擎內(nèi)部執(zhí)行的卻不是x86 指令,而是一條一條的類 RISC 指令,Intel 稱之為 Micro Operation——micro-op,或者寫(xiě) 為 μ-op,一般用比較方便的寫(xiě)法來(lái)替代掉希臘字母:u-op 或者 uop。相對(duì)地,一條一條的 x86 指令就稱之為 Macro Operation或 macro-op。
RISC 架構(gòu)的特點(diǎn)就是指令長(zhǎng)度相等,執(zhí)行時(shí)間恒定(通常為一個(gè)時(shí)鐘周期),因此處理器設(shè)計(jì)起來(lái)就很簡(jiǎn)單,可以通過(guò)深長(zhǎng)的流水線達(dá)到很高的頻率,IBM 的 Power6 就可以輕松地達(dá)到 4.7GHz 的起步頻率。和 RISC 相反,CISC 指令的長(zhǎng)度不固定,執(zhí)行時(shí)間也不固定,因此 Intel 的 RISC/CISC 混合處理器架構(gòu)就要通過(guò)解碼器 將 x86 指令翻譯為 uop,從而獲得 RISC 架構(gòu)的長(zhǎng)處,提升內(nèi)部執(zhí)行效率。
和 Core 一樣,Nehalem 的解碼器也是 4 個(gè)(3 個(gè)簡(jiǎn)單解碼器加 1 個(gè)復(fù)雜解碼器)。簡(jiǎn)單解碼器可以將一條 x86 指令(包括大部分 SSE 指令在內(nèi))翻譯為一條 uop,而復(fù)雜解碼器則將一些特別的(單條)x86 指令翻譯為 1~4 條 uops——在極少數(shù)的情況下,某些指令需要通過(guò) 額外的可編程 microcode 解碼器解碼為更多的 uops(有些時(shí)候甚至可以達(dá)到幾百個(gè),因?yàn)?一些 IA 指令很復(fù)雜,并且可以帶有很多的前綴/修改量,當(dāng)然這種情況很少見(jiàn)),下圖 Complex Decoder 左方的 ucode 方塊就是這個(gè)解碼器,這個(gè)解碼器可以通過(guò)一些途徑進(jìn)行升級(jí)或者擴(kuò)展,實(shí)際上就是通過(guò)主板 Firmware 里面的 Microcode ROM 部分。
之所以具有兩種解碼器,是因?yàn)槿匀皇顷P(guān)于 RISC/CISC 的一個(gè)事實(shí): 大部分情況下(90%) 的時(shí)間內(nèi)處理器都在運(yùn)行少數(shù)的指令,其余的時(shí)間則運(yùn)行各式各樣的復(fù)雜指令(不幸的是, 復(fù)雜就意味著較長(zhǎng)的運(yùn)行時(shí)間),RISC 就是將這些復(fù)雜的指令剔除掉,只留下最經(jīng)常運(yùn)行的指令(所謂的精簡(jiǎn)指令集),然而被剔除掉的那些指令雖然實(shí)現(xiàn)起來(lái)比較麻煩,卻在某些領(lǐng)域確實(shí)有其價(jià)值,RISC 的做法就是將這些麻煩都交給軟件,CISC 的做法則是像現(xiàn)在這樣: 由硬件設(shè)計(jì)完成。因此 RISC 指令集對(duì)編譯器要求很高,而 CISC 則很簡(jiǎn)單。對(duì)編程人員的要求也類似。
?
3、循環(huán)流檢測(cè)
在解碼為 uop 之后 Nehalem 會(huì)將它們都存放在一個(gè)叫做 uop LSD Buffer 的緩存區(qū)。在Core 2 上,這個(gè) LSD Buffer 是出現(xiàn)在解碼器前方的,Nehalem 將其移動(dòng)到解碼器后方,并相對(duì)加大了緩沖區(qū)的條目。Core 2 的 LSD 緩存區(qū)可以保存 18 個(gè) x86 指令而 Nehalem 可以保 存 28 個(gè) uop,從前文可以知道,大部分 x86 指令都可以解碼為一個(gè) uop,少部分可以解碼 為 1~4 個(gè) uop,因此 Nehalem 的 LSD 緩沖區(qū)基本上可以相當(dāng)于保存 21~23 條x86 指令,比 Core 2 要大上一些。
?
LSD 循環(huán)流監(jiān)測(cè)器也算包含在解碼部分,它的作用是: 假如程序使用的循環(huán)段(如 for..do/do..while 等)少于 28 個(gè) uops,那么 Nehalem 就可以將這個(gè)循環(huán)保存起來(lái),不再需要重新通過(guò)取指單元、分支預(yù)測(cè)操作,以及解碼器,Core 2 的 LSD 放在解碼器前方,因此無(wú)法省下解碼的工作。
Nehalem LSD 的工作比較像 NetBurst 架構(gòu)的 Trace Cache,其也是保存 uops,作用也是部分地去掉一些嚴(yán)重的循環(huán),不過(guò)由于 Trace Cache 還同時(shí)擔(dān)當(dāng)著類似于 Core/Nehalem 架構(gòu)的 Reorder Buffer 亂序緩沖區(qū)的作用,容量比較大(可以保存 12k uops,準(zhǔn)確的大小 是 20KB),因此在 cache miss 的時(shí)候后果嚴(yán)重(特別是在 SMT 同步多線程之后,miss 率加 倍的情況下),LSD 的小數(shù)目設(shè)計(jì)顯然會(huì)好得多。不過(guò)筆者認(rèn)為 28 個(gè) uop 條目有些少,特 別是考慮到 SMT 技術(shù)帶來(lái)的兩條線程都同時(shí)使用這個(gè) LSD 的時(shí)候。
在 LSD 之后,Nehalem 將會(huì)進(jìn)行 Micro-ops Fusion,這也是前端(The Front-End)的最后一個(gè)功能,在這些工作都做完之后,uops 就可以準(zhǔn)備進(jìn)入執(zhí)行引擎了。
4.亂序執(zhí)行指令階段(OoOE)
OoOE— Out-of-Order Execution 亂序執(zhí)行也是在 Pentium Pro 開(kāi)始引入的,它有些類似于多線程的概念。亂序執(zhí)行是為了直接提升 ILP(Instruction Level Parallelism)指令級(jí)并行化的設(shè)計(jì),在多個(gè)執(zhí)行單元的超標(biāo)量設(shè)計(jì)當(dāng)中,一系列的執(zhí)行單元可以同時(shí)運(yùn)行一些沒(méi)有數(shù)據(jù)關(guān)聯(lián)性的若干指令,只有需要等待其他指令運(yùn)算結(jié)果的數(shù)據(jù)會(huì)按照順序執(zhí)行,從而總體提升了運(yùn)行效率。亂序執(zhí)行引擎是一個(gè)很重要的部分,需要進(jìn)行復(fù)雜的調(diào)度管理。
首先,在亂序執(zhí)行架構(gòu)中,不同的指令可能都會(huì)需要用到相同的通用寄存器(GPR,General Purpose Registers),特別是在指令需要改寫(xiě)該通用寄存器的情況下——為了讓這些指令們能并行工作,處理器需要準(zhǔn)備解決方法。一般的 RISC 架構(gòu)準(zhǔn)備了大量的GPR, 而x86 架構(gòu)天生就缺乏 GPR(x86具有8個(gè)GPR,x86-64 具有 16 個(gè),一般 RISC 具有 32 個(gè),IA64 則具有 128 個(gè)),為此 Intel 開(kāi)始引入重命名寄存器(Rename Register),不同的指令可以通過(guò)具有名字相同但實(shí)際不同的寄存器來(lái)解決。
此外,為了 SMT 同步多線程,這些寄存器還要準(zhǔn)備雙份,每個(gè)線程具有獨(dú)立的一份。
?
亂序執(zhí)行從Allocator定位器開(kāi)始,Allocator 管理著RAT(Register Alias Table,寄存器別名表)、ROB(Re-Order Buffer,重排序緩沖區(qū))和 RRF(Retirement Register File,退回寄存器文件)。在 Allocator 之前,流水線都是順序執(zhí)行的,在 Allocator 之后,就可以進(jìn)入亂序執(zhí)行階段了。在每一個(gè)線程方面,Nehalem 和 Core 2 架構(gòu)相似,RAT 將重命名的、虛擬的寄存器(稱為 Architectural Register 或 Logical Register)指向ROB 或者RRF。RAT 是一式兩份,每個(gè)線程獨(dú)立,每個(gè) RAT 包含了 128 個(gè)重命名寄存器。RAT 指向在 ROB 里面的最近的執(zhí)行寄存器狀態(tài),或者指向RRF保存的最終的提交狀態(tài)。
ROB(Re-Order Buffer,重排序緩沖區(qū))是一個(gè)非常重要的部件,它是將亂序執(zhí)行完畢的指令們按照程序編程的原始順序重新排序的一個(gè)隊(duì)列,以保證所有的指令都能夠邏輯上實(shí)現(xiàn)正確的因果關(guān)系。打亂了次序的指令們(分支預(yù)測(cè)、硬件預(yù)取)依次插入這個(gè)隊(duì)列,當(dāng)一條指令通過(guò) RAT 發(fā)往下一個(gè)階段確實(shí)執(zhí)行的時(shí)候這條指令(包括寄存器狀態(tài)在內(nèi))將被加入 ROB 隊(duì)列的一端,執(zhí)行完畢的指令(包括寄存器狀態(tài))將從 ROB 隊(duì)列的另一端移除(期間這些指令的數(shù)據(jù)可以被一些中間計(jì)算結(jié)果刷新),因?yàn)檎{(diào)度器是 In-Order 順序的,這個(gè)隊(duì)列(ROB)也就是順序的。從 ROB 中移出一條指令就意味著指令執(zhí)行完畢了,這個(gè)階段叫做 Retire 回退,相應(yīng)地 ROB 往往也叫做 Retirement Unit(回退單元),并將其畫(huà)為流水線的最后一部分。
在一些超標(biāo)量設(shè)計(jì)中,Retire 階段會(huì)將 ROB 的數(shù)據(jù)寫(xiě)入 L1D 緩存(這是將MOB集成到ROB的情況),而在另一些設(shè)計(jì)里, 寫(xiě)入 L1D 緩存由另外的隊(duì)列完成。例如,Core/Nehalem 的這個(gè)操作就由 MOB(Memory Order Buffer,內(nèi)存重排序緩沖區(qū))來(lái)完成。
ROB 是亂序執(zhí)行引擎架構(gòu)中都存在的一個(gè)緩沖區(qū),重新排序指令的目的是將指令們的寄存器狀態(tài)依次提交到RRF退回寄存器文件當(dāng)中,以確保具有因果關(guān)系的指令們?cè)趤y序執(zhí)行中可以得到正確的數(shù)據(jù)。從執(zhí)行單元返回的數(shù)據(jù)會(huì)將先前由調(diào)度器加入ROB 的指令刷新數(shù)據(jù)部分并標(biāo)志為結(jié)束(Finished),再經(jīng)過(guò)其他檢查通過(guò)后才能標(biāo)志為完畢(Complete),一旦標(biāo)志為完畢,它就可以提交數(shù)據(jù)并刪除重命名項(xiàng)目并退出ROB 了。提交狀態(tài)的工作由 Retirement Unit(回退單元)完成,它將確實(shí)完畢的指令包含的數(shù)據(jù)寫(xiě)入RRF(“確實(shí)” 的意思是,非猜測(cè)執(zhí)性、具備正確因果關(guān)系,程序可以見(jiàn)到的最終的寄存器狀態(tài))。和 RAT 一樣,RRF 也同時(shí)具有兩個(gè),每個(gè)線程獨(dú)立。Core/Nehalem 的 Retirement Unit 回退單元每時(shí)鐘周期可以執(zhí)行 4 個(gè) uops 的寄存器文件寫(xiě)入,和 RAT 每時(shí)鐘 4 個(gè) uops 的重命名一致。
由于 ROB 里面保存的指令數(shù)目是如此之大(128 條目),因此一些人認(rèn)為它的作用是用來(lái)從中挑選出不相關(guān)的指令來(lái)進(jìn)入執(zhí)行單元,這多少是受到一些文檔中的 Out-of-Order Window 亂序窗口這個(gè)詞的影響(后面會(huì)看到ROB 會(huì)和 MOB 一起被計(jì)入亂序窗口資源中)。
ROB 確實(shí)具有 RS 的一部分相似的作用,不過(guò),ROB 里面的指令是調(diào)度器(dispacher)通過(guò) RAT發(fā)往 RS 的同時(shí)發(fā)往ROB的(里面包含著正常順序的指令和猜測(cè)執(zhí)行的指令,但是亂序執(zhí)行并不是從ROB中亂序挑選的),也就是說(shuō),在“亂序”之前,ROB 的指令就已經(jīng)確定了。指令并不是在 ROB 當(dāng)中亂序挑選的(這是在RS當(dāng)中進(jìn)行),ROB 擔(dān)當(dāng)?shù)氖橇魉€的最終階段: 一個(gè)指令的 Retire回退單元;以及擔(dān)當(dāng)中間計(jì)算結(jié)果的緩沖區(qū)。 RS(Reservation Station,中繼站): 等待源數(shù)據(jù)到來(lái)以進(jìn)行OoOE亂序執(zhí)行(沒(méi)有數(shù)據(jù)的指令將在 RS 等待), ROB(ReOrder Buffer,重排序緩沖區(qū)): 等待結(jié)果到達(dá)以進(jìn)行 Retire 指令回退 (沒(méi)有結(jié)果的指令將在 ROB等待)。
Nehalem 的 128 條目的 ROB 擔(dān)當(dāng)中間計(jì)算結(jié)果的緩沖區(qū),它保存著猜測(cè)執(zhí)行的指令及其數(shù)據(jù),猜測(cè)執(zhí)行允許預(yù)先執(zhí)行方向未定的分支指令。在大部分情況下,猜測(cè)執(zhí)行工作良好——分支猜對(duì)了,因此其在 ROB 里產(chǎn)生的結(jié)果被標(biāo)志為已結(jié)束,可以立即地被后繼指令使用而不需要進(jìn)行 L1 Data Cache 的 Load 操作(這也是 ROB 的另一個(gè)重要用處,典型的 x86 應(yīng)用中 Load 操作是如此頻繁,達(dá)到了幾乎占 1/3 的地步,因此 ROB 可以避免大量的Cache Load 操作,作用巨大)。在剩下的不幸的情況下,分支未能按照如期的情況進(jìn)行,這時(shí)猜測(cè)的分支指令段將被清除,相應(yīng)指令們的流水線階段清空,對(duì)應(yīng)的寄存器狀態(tài)也就全都無(wú)效了,這種無(wú)效的寄存器狀態(tài)不會(huì)也不能出現(xiàn)在 RRF 里面。
重命名技術(shù)并不是沒(méi)有代價(jià)的,在獲得前面所說(shuō)的眾多的優(yōu)點(diǎn)之后,它令指令在發(fā)射的時(shí)候需要掃描額外的地方來(lái)尋找到正確的寄存器狀態(tài),不過(guò)總體來(lái)說(shuō)這種代價(jià)是非常值得的。RAT可以在每一個(gè)時(shí)鐘周期重命名 4 個(gè) uops 的寄存器,經(jīng)過(guò)重命名的指令在讀取到正確的操作數(shù)并發(fā)射到統(tǒng)一的RS(Reservation Station,中繼站,Intel 文檔翻譯為保留站點(diǎn)) 上。RS 中繼站保存了所有等待執(zhí)行的指令。
和 Core 2 相比,Nehalem 的 ROB 大小和 RS 大小都得到了提升,ROB 重排序緩沖區(qū)從 96 條目提升到 128 條目(鼻祖 Pentium Pro 具有 40 條),RS 中繼站從 32 提升到 36(Pentium Pro 為 20),它們都在兩個(gè)線程(超線程中的線程)內(nèi)共享,不過(guò)采用了不同的策略:ROB 是采用了靜態(tài)的分區(qū)方法,而 RS 則采用了動(dòng)態(tài)共享,因?yàn)橛袝r(shí)候會(huì)有一條線程內(nèi)的指令因 等待數(shù)據(jù)而停滯,這時(shí)另一個(gè)線程就可以獲得更多的 RS 資源。停滯的指令不會(huì)發(fā)往 RS,但是仍然會(huì)占用 ROB 條目。由于 ROB 是靜態(tài)分區(qū),因此在開(kāi)啟 HTT 的情況下,每一個(gè)線程只能 分到 64 條,不算多,在一些極少數(shù)的應(yīng)用上,我們應(yīng)該可以觀察到一些應(yīng)用開(kāi)啟 HTT 后會(huì) 速度降低,盡管可能非常微小。
5、執(zhí)行單元
在為 SMT 做好準(zhǔn)備工作并打亂指令的執(zhí)行順序之后(指的是分支預(yù)測(cè)、硬件預(yù)取),uops 通過(guò)每時(shí)鐘周期 4 條的速度進(jìn)入 Reservation Station 中繼站(保留站),總共 36 條目的中繼站 uops 就開(kāi)始等待超標(biāo)量(Superscaler)執(zhí)行引擎亂序執(zhí)行了。自從 Pentium 開(kāi)始,Intel 就開(kāi)始在處理器里面采用了超標(biāo)量設(shè)計(jì)(Pentium 是兩路超標(biāo)量處理器),超標(biāo)量的意思就是多個(gè)執(zhí)行單元,它可以同時(shí)執(zhí)行多條沒(méi)有相互依賴性的指令,從而達(dá)到提升 ILP 指令級(jí)并行化的目的。Nehalem 具備 6 個(gè)執(zhí)行端口,每個(gè)執(zhí)行端口具有多個(gè)不同的單元以執(zhí)行不同的任務(wù),然而同一時(shí)間只能有一條指令(uop)進(jìn)入執(zhí)行端口,因此也可以認(rèn)為 Nehalem 有 6 個(gè)“執(zhí)行單元”,在每個(gè)時(shí)鐘周期內(nèi)可以執(zhí)行最多 6 個(gè)操作(或者說(shuō),6 條指令),和 Core 一樣。
?
36 條目的中繼站指令在分發(fā)器的管理下,挑選出盡量多的可以同時(shí)執(zhí)行的指令(也就是亂序執(zhí)行的意思)——最多 6 條——發(fā)送到執(zhí)行端口。 這些執(zhí)行端口并不都是用于計(jì)算,實(shí)際上,有三個(gè)執(zhí)行端口是專門用來(lái)執(zhí)行內(nèi)存相關(guān)的操作的,只有剩下的三個(gè)是計(jì)算端口,因此,在這一點(diǎn)上 Nehalem 實(shí)際上是跟 Core 架構(gòu)一 樣的,這也可以解釋為什么有些情況下,Nehalem 和 Core 相比沒(méi)有什么性能提升。
計(jì)算操作分為兩種: 使用 ALU(Arithmetic Logic Unit,算術(shù)邏輯單元)的整數(shù)(Integer) 運(yùn)算和使用 FPU(Floating Point Unit,浮點(diǎn)運(yùn)算單元)的浮點(diǎn)(Floating Point)運(yùn)算。SSE 指令(包括 SSE1 到 SSE4)是一種特例,它雖然有整數(shù)也有浮點(diǎn),然而它們使用的都是 128bit 浮點(diǎn)寄存器,使用的也大部分是 FPU 電路。在 Nehalem 中,三個(gè)計(jì)算端口都可以做整數(shù)運(yùn)算(包括 MMX)或者SSE 運(yùn)算(浮點(diǎn)運(yùn)算不太一樣,只有兩個(gè)端口可以進(jìn)行浮點(diǎn) ADD 和 MUL/DIV 運(yùn)算,因此每時(shí)鐘周期最多進(jìn)行 2 個(gè)浮點(diǎn)計(jì)算,這也是目前 Intel 處理器浮點(diǎn)性能不如整數(shù)性能突出的原因),不過(guò)每一個(gè)執(zhí)行端口都不是完全一致:只有端口 0 有浮點(diǎn)乘和除功能,只有端口 5 有分支能力(這個(gè)執(zhí)行單元將會(huì)與分支預(yù)測(cè)單元連接),其他 FP/SSE 能力也不盡相同,這些不對(duì)稱之處都由統(tǒng)一的分發(fā)器來(lái)理解,并進(jìn)行指令的調(diào)度管理。沒(méi)有采用完全對(duì)稱的設(shè)計(jì)可能是基于統(tǒng)計(jì)學(xué)上的考慮。和 Core 一樣,Nehalem 的也沒(méi)有采用 Pentium 4 那樣的 2 倍頻的 ALU 設(shè)計(jì)(在 Pentium 4,ALU 的運(yùn)算頻率是 CPU 主頻的兩倍, 因此整數(shù)性能明顯要比浮點(diǎn)性能突出)。
不幸的是,雖然可以同時(shí)執(zhí)行的指令很多,然而在流水線架構(gòu)當(dāng)中運(yùn)行速度并不是由最 “寬”的單元來(lái)決定的,而是由最“窄”的單元來(lái)決定的。這就是木桶原理,Opteron的解碼器后端只能每時(shí)鐘周期輸出 3 條 uops,而 Nehalem/Core2 則能輸出 4 條,因此它們的實(shí)際最大每時(shí)鐘運(yùn)行指令數(shù)是 3/4,而不是 6。同樣地,多少路超標(biāo)量在這些亂序架構(gòu)處理器中也不再按照運(yùn)算單元來(lái)劃分,Core Duo 及之前(到 Pentium Pro 為止)均為三路超標(biāo)量處理器,Core 2/Nehalem 則為四路超標(biāo)量處理器??梢?jiàn)在微架構(gòu)上,Nehalem/Core 顯然是 要比其他處理器快一些。順便說(shuō)一下,這也是 Intel 在超線程示意圖中,使用 4 個(gè)寬度的方 塊來(lái)表示而不是 6 個(gè)方塊的原因。
6、存取單元
運(yùn)算需要用到數(shù)據(jù),也會(huì)生成數(shù)據(jù),這些數(shù)據(jù)存取操作就是存取單元所做的事情,實(shí)際 上,Nehalem 和 Core 的存取單元沒(méi)什么變化,仍然是 3 個(gè)。
這三個(gè)存取單元中,一個(gè)用于所有的 Load 操作(地址和數(shù)據(jù)),一個(gè)用于 Store 地址,一個(gè)用于 Store 數(shù)據(jù),前兩個(gè)數(shù)據(jù)相關(guān)的單元帶有 AGU(Address Generation Unit,地址生成單元)功能(NetBurst架構(gòu)使用快速 ALU 來(lái)進(jìn)行地址生成)。
?
在亂序架構(gòu)中,存取操作也可以打亂進(jìn)行。類似于指令預(yù)取一樣,Load/Store 操作也可以提前進(jìn)行以降低延遲的影響,提高性能。然而,由于Store操作會(huì)修改數(shù)據(jù)影響后繼的Load 操作,而指令卻不會(huì)有這種問(wèn)題(寄存器依賴性問(wèn)題通過(guò)ROB解決),因此數(shù)據(jù)的亂序操作更為復(fù)雜。
?
如上圖所示,第一條 ALU 指令的運(yùn)算結(jié)果要 Store 在地址 Y(第二條指令),而第九條 指令是從地址 Y Load 數(shù)據(jù),顯然在第二條指令執(zhí)行完畢之前,無(wú)法移動(dòng)第九條指令,否則將會(huì)產(chǎn)生錯(cuò)誤的結(jié)果。同樣,如果CPU也不知道第五條指令會(huì)使用什么地址,所以它也無(wú)法確定是否可以把第九條指令移動(dòng)到第五條指令附近。
?
內(nèi)存數(shù)據(jù)相依性預(yù)測(cè)功能(Memory Disambiguation)可以預(yù)測(cè)哪些指令是具有依賴性的或者使用相關(guān)的地址(地址混淆,Alias),從而決定哪些 Load/Store 指令是可以提前的, 哪些是不可以提前的??梢蕴崆暗闹噶钤谄浜罄^指令需要數(shù)據(jù)之前就開(kāi)始執(zhí)行、讀取數(shù)據(jù)到ROB當(dāng)中,這樣后繼指令就可以直接從中使用數(shù)據(jù),從而避免訪問(wèn)了無(wú)法提前 Load/Store 時(shí)訪問(wèn) L1 緩存帶來(lái)的延遲(3~4 個(gè)時(shí)鐘周期)。
不過(guò),為了要判斷一個(gè) Load 指令所操作的地址沒(méi)有問(wèn)題,緩存系統(tǒng)需要檢查處于 in-flight 狀態(tài)(處理器流水線中所有未執(zhí)行的指令)的 Store 操作,這是一個(gè)頗耗費(fèi)資源的過(guò)程。在 NetBurst 微架構(gòu)中,通過(guò)把一條 Store 指令分解為兩個(gè) uops——一個(gè)用于計(jì)算地址、一個(gè)用于真正的存儲(chǔ)數(shù)據(jù),這種方式可以提前預(yù)知 Store 指令所操作的地址,初步的解決了數(shù)據(jù)相依性問(wèn)題。在 NetBurst 微架構(gòu)中,Load/Store 亂序操作的算法遵循以下幾條 原則:
如果一個(gè)對(duì)于未知地址進(jìn)行操作的 Store 指令處于 in-flight 狀態(tài),那么所有的 Load 指令都要被延遲
在操作相同地址的 Store 指令之前 Load 指令不能繼續(xù)執(zhí)行
一個(gè) Store 指令不能移動(dòng)到另外一個(gè) Store 指令之前(指的是在RS中不能先挑選執(zhí)行后面的一條store指令,注意這只是說(shuō)某一種架構(gòu)不允許重排store,其實(shí)還是有很多架構(gòu)如Alpha等是松散內(nèi)存模型,允許不相關(guān)的store重排序的.)
這種原則下的問(wèn)題也很明顯,比如第一條原則會(huì)在一條處于等待狀態(tài)的 Store 指令所操作的地址未確定之前,就延遲所有的 Load 操作,顯然過(guò)于保守了。實(shí)際上,地址沖突問(wèn)題是極少發(fā)生的。根據(jù)某些機(jī)構(gòu)的研究,在一個(gè)Alpha EV6 處理器中最多可以允許 512 條指令處于 in-flight 狀態(tài),但是其中的 97%以上的 Load 和 Store 指令都不會(huì)存在地址沖突問(wèn)題。
基于這種理念,Core 微架構(gòu)采用了大膽的做法,它令 Load 指令總是提前進(jìn)行,除非新加入的動(dòng)態(tài)混淆預(yù)測(cè)器(Dynamic Alias Predictor)預(yù)測(cè)到了該 Load 指令不能被移動(dòng)到 Store 指令附近。這個(gè)預(yù)測(cè)是根據(jù)歷史行為來(lái)進(jìn)行的,據(jù)說(shuō)準(zhǔn)確率超過(guò) 90%。
在執(zhí)行了預(yù) Load 之后,一個(gè)沖突監(jiān)測(cè)器會(huì)掃描 MOB 的 Store 隊(duì)列,檢查該是否有Store操作與該 Load 沖突。在很不幸的情況下(1%~2%),發(fā)現(xiàn)了沖突,那么該 Load 操作作廢、 流水線清除并重新進(jìn)行 Load 操作。這樣大約會(huì)損失 20 個(gè)時(shí)鐘周期的時(shí)間,然而從整體上看, Core 微架構(gòu)的激進(jìn) Load/Store 亂序策略確實(shí)很有效地提升了性能,因?yàn)長(zhǎng)oad 操作占據(jù)了通常程序的 1/3 左右,并且 Load 操作可能會(huì)導(dǎo)致巨大的延遲(在命中的情況下,Core 的 L1D Cache 延遲為 3 個(gè)時(shí)鐘周期,Nehalem 則為 4 個(gè)。L1 未命中時(shí)則會(huì)訪問(wèn) L2 緩存,一般為 10~12 個(gè)時(shí)鐘周期。訪問(wèn) L3 通常需要 30~40 個(gè)時(shí)鐘周期,訪問(wèn)主內(nèi)存則可以達(dá)到最多約 100 個(gè)時(shí)鐘周期)。Store 操作并不重要,什么時(shí)候?qū)懭氲?L1 乃至主內(nèi)存并不會(huì)影響到執(zhí)行性能。
?
如上圖所示,我們需要載入地址 X 的數(shù)據(jù),加 1 之后保存結(jié)果;載入地址 Y 的數(shù)據(jù),加1 之后保存結(jié)果;載入地址 Z 的數(shù)據(jù),加 1 之后保存結(jié)果。如果根據(jù) Netburst 的基本準(zhǔn)則, 在第三條指令未決定要存儲(chǔ)在什么地址之前,處理器是不能移動(dòng)第四條指令和第七條指令的。實(shí)際上,它們之間并沒(méi)有依賴性。因此,Core 微架構(gòu)中則“大膽”的將第四條指令和第七條指令分別移動(dòng)到第二和第三指令的并行位置,這種行為是基于一定的猜測(cè)的基礎(chǔ)上的“投機(jī)”行為,如果猜測(cè)的對(duì)的話(幾率在 90%以上),完成所有的運(yùn)算只要5個(gè)周期,相比之前的9個(gè)周期幾乎快了一倍。
和為了順序提交到寄存器而需要 ROB 重排序緩沖區(qū)的存在一樣,在亂序架構(gòu)中,多個(gè)打亂了順序的 Load 操作和Store操作也需要按順序提交到內(nèi)存,MOB(Memory Reorder Buffer, 內(nèi)存重排序緩沖區(qū))就是起到這樣一個(gè)作用的重排序緩沖區(qū)(介于 Load/Store 單元 與 L1D Cache 之間的部件,有時(shí)候也稱之為L(zhǎng)SQ),MOB 通過(guò)一個(gè) 128bit 位寬的 Load 通道與一個(gè) 128bit 位寬的 Store 通道與雙口 L1D Cache 通信。和 ROB 一樣,MOB的內(nèi)容按照 Load/Store 指令實(shí)際的順序加入隊(duì)列的一端,按照提交到 L1 DCache 的順序從隊(duì)列的另一端移除。ROB 和 MOB 一起實(shí)際上形成了一個(gè)分布式的 Order Buffer 結(jié)構(gòu),有些處理器上只存在 ROB,兼?zhèn)淞?MOB 的功能(把MOB看做ROB的一部分可能更好理解)。
和ROB 一樣,Load/Store 單元的亂序存取操作會(huì)在 MOB 中按照原始程序順序排列,以提供正確的數(shù)據(jù),內(nèi)存數(shù)據(jù)依賴性檢測(cè)功能也在里面實(shí)現(xiàn)(內(nèi)存數(shù)據(jù)依賴性的檢測(cè)比指令寄存器間的依賴性檢測(cè)要復(fù)雜的多)。MOB 的 Load/Store 操作結(jié)果也會(huì)直接反映到 ROB當(dāng)中(中間結(jié)果)。
MOB還附帶了數(shù)據(jù)預(yù)取(Data Prefetch)功能,它會(huì)猜測(cè)未來(lái)指令會(huì)使用到的數(shù)據(jù),并預(yù)先從L1D Cache 緩存 Load入MOB 中(Data Prefetcher 也會(huì)對(duì) L2 至系統(tǒng)內(nèi)存的數(shù)據(jù)進(jìn)行這樣的操作), 這樣 MOB 當(dāng)中的數(shù)據(jù)有些在 ROB 中是不存在的(這有些像 ROB 當(dāng)中的 Speculative Execution 猜測(cè)執(zhí)行,MOB 當(dāng)中也存在著“Speculative Load Execution 猜測(cè)載入”,只不過(guò)失敗的猜測(cè)執(zhí)行會(huì)導(dǎo)致管線停頓,而失敗的猜測(cè)載入僅僅會(huì)影響到性能,然而前端時(shí)間發(fā)生的Meltdown漏洞卻造成了嚴(yán)重的安全問(wèn)題)。MOB包括了Load Buffers和Store Buffers。
亂序執(zhí)行中我們可以看到很多緩沖區(qū)性質(zhì)的東西: RAT 寄存器別名表、ROB 重排序緩沖 區(qū)、RS 中繼站、MOB 內(nèi)存重排序緩沖區(qū)(包括 load buffer 載入緩沖和 store buffer 存儲(chǔ)緩沖)。在超線程的作 用下,RAT是一式兩份,包含了 128 個(gè)重命名寄存器; 128 條目的 ROB、48 條目的 LB 和 32 條目的 SB 都 每個(gè)線程 64 個(gè) ROB、24 個(gè) LB 和 16 個(gè) SB; RS 則是在兩個(gè)線程中動(dòng)態(tài)共享??梢?jiàn),雖然整體數(shù)量增加了,然而就單個(gè)線程而言,獲得的資源并沒(méi)有 提升。這會(huì)影響到 HTT 下單線程下的性能。
六、緩存(cache)
通常緩存具有兩種設(shè)計(jì):非獨(dú)占和獨(dú)占,Nehalem 處理器的 L3 采用了非獨(dú)占高速緩存 設(shè)計(jì)(或者說(shuō)“包含式”,L3 包含了 L1/L2 的內(nèi)容),這種方式在 Cache Miss 的時(shí)候比獨(dú) 占式具有更好的性能,而在緩存命中的時(shí)候需要檢查不同的核心的緩存一致性。Nehalem 并 采用了“內(nèi)核有效”數(shù)據(jù)位的額外設(shè)計(jì),降低了這種檢查帶來(lái)的性能影響。隨著核心數(shù)目的 逐漸增多(多線程的加入也會(huì)增加 Cache Miss 率),對(duì)緩存的壓力也會(huì)繼續(xù)增大,因此這 種方式會(huì)比較符合未來(lái)的趨勢(shì)。在后面可以看到,這種設(shè)計(jì)也是考慮到了多處理器協(xié)作的情況(此時(shí) Miss 率會(huì)很容易地增加)。這可以看作是 Nehalem 與以往架構(gòu)的基礎(chǔ)不同:之前的架構(gòu)都是來(lái)源于移動(dòng)處理設(shè)計(jì),而 Nehalem 則同時(shí)為企業(yè)、桌面和移動(dòng)考慮而設(shè)計(jì)。
在 L3 緩存命中的時(shí)候(單處理器上是最通常的情況,多處理器下則不然),處理器檢查內(nèi)核有效位看看是否其他內(nèi)核也有請(qǐng)求的緩存頁(yè)面內(nèi)容,決定是否需要對(duì)內(nèi)核進(jìn)行偵聽(tīng)。
在NUMA架構(gòu)中,多個(gè)處理器中的同一個(gè)緩存頁(yè)面必定在其中一個(gè)處理器中屬于 F 狀態(tài)(可以修改的狀態(tài)),這個(gè)頁(yè)面在這個(gè)處理器中沒(méi)有理由不可以多核心共享(可以多核心共享就意味著這個(gè)能進(jìn)入修改狀態(tài)的頁(yè)面的多個(gè)有效位被設(shè)置為一)。MESIF協(xié)議應(yīng)該是工作在核心(L1+L2)層面而不是處理器(L3)層面,這樣同一處理器里多個(gè)核心共享的頁(yè)面,只有其中一個(gè)是出于 F 狀態(tài)(可以修改的狀態(tài))。見(jiàn)后面對(duì) NUMA 和 MESIF 的解析。(L1/L2/L3 的同步應(yīng)該是不需要 MESIF 的同步機(jī)制)
在 L3 緩存未命中的時(shí)候(多處理器下會(huì)頻繁發(fā)生),處理器決定進(jìn)行內(nèi)存存取,按照 頁(yè)面的物理位置,它分為近端內(nèi)存存取(本地內(nèi)存空間)和遠(yuǎn)端內(nèi)存存取(地址在其他處理 器的內(nèi)存的空間):
七、緩存Cache架構(gòu)原理
Cache的容量很小,它保存的內(nèi)容只是主存內(nèi)容的一個(gè)子集,且Cache與主存的數(shù)據(jù)交換是以塊為單位的。為了把信息放到Cache中,必須應(yīng)用某種函數(shù)把主存地址定位到Cache中,這稱為地址映射。在信息按這種映射關(guān)系裝入Cache后,CPU執(zhí)行程序時(shí),會(huì)將程序中的主存地址變換成Cache地址,這個(gè)變換過(guò)程叫做地址變換。
Cache的地址映射方式有直接映射、全相聯(lián)映射和組相聯(lián)映射。假設(shè)某臺(tái)計(jì)算機(jī)主存容量為l MB,被分為2048塊,每塊512B;Cache容量為8KB,被分為16塊,每塊也是512B。下面以此為例介紹三種基本的地址映射方法。
直接映射
直接映射的Cache組織如圖3-14所示。主存中的一個(gè)塊只能映射到Cache的某一特定塊中去。例如,主存的第0塊、第16塊、……、第2032塊,只能映射到Cache的第0塊;而主存的第1塊、第17塊、……、第2033塊,只能映射到Cache的第1塊……。
?
直接映射是最簡(jiǎn)單的地址映射方式,它的硬件簡(jiǎn)單,成本低,地址變換速度快,而且不涉及替換算法問(wèn)題。但是這種方式不夠靈活,Cache的存儲(chǔ)空間得不到充分利用,每個(gè)主存塊只有一個(gè)固定位置可存放,容易產(chǎn)生沖突,使Cache效率下降,因此只適合大容量Cache采用。例如,如果一個(gè)程序需要重復(fù)引用主存中第0塊與第16塊,最好將主存第0塊與第16塊同時(shí)復(fù)制到Cache中,但由于它們都只能復(fù)制到Cache的第0塊中去,即使Cache中別的存儲(chǔ)空間空著也不能占用,因此這兩個(gè)塊會(huì)不斷地交替裝入Cache中,導(dǎo)致命中率降低。
全相聯(lián)映射
圖3-15 是全相聯(lián)映射的Cache組織,主存中任何一塊都可以映射到Cache中的任何一塊位置上。
?
全相聯(lián)映射方式比較靈活,主存的各塊可以映射到Cache的任一塊中,Cache的利用率高,塊沖突概率低,只要淘汰Cache中的某一塊,即可調(diào)入主存的任一塊。但是,由于Cache比較電路的設(shè)計(jì)和實(shí)現(xiàn)比較困難,這種方式只適合于小容量Cache采用。
組相聯(lián)映射
組相聯(lián)映射實(shí)際上是直接映射和全相聯(lián)映射的折中方案,其組織結(jié)構(gòu)如圖3-16所示。主存和Cache都分組,主存中一個(gè)組內(nèi)的塊數(shù)與Cache中的分組數(shù)相同,組間采用直接映射,組內(nèi)采用全相聯(lián)映射。也就是說(shuō),將Cache分成u組,每組v塊,主存塊存放到哪個(gè)組是固定的,至于存到該組哪一塊則是靈活的。例如,主存分為256組,每組8塊,Cache分為8組,每組2塊。
?
主存中的各塊與Cache的組號(hào)之間有固定的映射關(guān)系,但可自由映射到對(duì)應(yīng)Cache組中的任何一塊。例如,主存中的第0塊、第8塊……均映射于Cache的第0組,但可映射到Cache第0組中的第0塊或第1塊;主存的第1塊、第9塊……均映射于Cache的第1組,但可映射到Cache第1組中的第2塊或第3塊。
常采用的組相聯(lián)結(jié)構(gòu)Cache,每組內(nèi)有2、4、8、16塊,稱為2路、4路、8路、16路組相聯(lián)Cache。組相聯(lián)結(jié)構(gòu)Cache是前兩種方法的折中方案,適度兼顧二者的優(yōu)點(diǎn),盡量避免二者的缺點(diǎn),因而得到普遍采用。
一次內(nèi)存訪問(wèn)示意圖
?
?
注意事項(xiàng)
TLB采用組相聯(lián)
頁(yè)表采用兩級(jí)頁(yè)表
cache采用組相聯(lián)
cache僅考慮L1 d-cache,不考慮L1 i-cache、L2 cache和L3 cache
未考慮頁(yè)表缺頁(yè)
簡(jiǎn)化了cache未命中情況
實(shí)際例子
下面展示了現(xiàn)代Intel處理器的CPU cache是如何組織的。有關(guān)cache的討論往往缺乏具體的實(shí)例,使得一些簡(jiǎn)單的概念變得撲朔迷離。也許是我可愛(ài)的小腦瓜有點(diǎn)遲鈍吧,但不管怎樣,至少下面講述了故事的前一半,即Core 2的 L1 cache是如何被訪問(wèn)的:
?
L1 cache – 32KB,8路組相聯(lián),64字節(jié)緩存線
?
?
?
1. 由索引揀選緩存組(行)
在cache中的數(shù)據(jù)是以緩存線(line)為單位組織的,一條緩存線對(duì)應(yīng)于內(nèi)存中一個(gè)連續(xù)的字節(jié)塊。這個(gè)cache使用了64字節(jié)的緩存線。這些線被保存在cache bank中,也叫路(way)。每一路都有一個(gè)專門的目錄(directory)用來(lái)保存一些登記信息。你可以把每一路連同它的目錄想象成電子表格中的一列,而表的一行構(gòu)成了cache的一組(set)。列中的每一個(gè)單元(cell)都含有一條緩存線,由與之對(duì)應(yīng)的目錄單元跟蹤管理。圖中的cache有64 組、每組8路,因此有512個(gè)含有緩存線的單元,合計(jì)32KB的存儲(chǔ)空間。
在cache眼中,物理內(nèi)存被分割成了許多4KB大小的物理內(nèi)存頁(yè)(page)。每一頁(yè)都含有4kb/64/bytes== 64條緩存線。在一個(gè)4KB的頁(yè)中,第0到63字節(jié)是第一條緩存線,第64到127字節(jié)是第二條緩存線,以此類推。每一頁(yè)都重復(fù)著這種劃分,所以第0頁(yè)第3條緩存線與第1頁(yè)第3條緩存線是不同的。
在全相聯(lián)緩存(fully associative cache)中,內(nèi)存中的任意一條緩存線都可以被存儲(chǔ)到任意的緩存單元中。這種存儲(chǔ)方式十分靈活,但也使得要訪問(wèn)它們時(shí),檢索緩存單元的工作變得復(fù)雜、昂貴。由于L1和L2 cache工作在很強(qiáng)的約束之下,包括功耗,芯片物理空間,存取速度等,所以在多數(shù)情況下,使用全相聯(lián)緩存并不是一個(gè)很好的折中。
取而代之的是圖中的組相聯(lián)緩存(set associative cache)。意思是,內(nèi)存中一條給定的緩存線只能被保存在一個(gè)特定的組(或行)中。所以,任意物理內(nèi)存頁(yè)的第0條緩存線(頁(yè)內(nèi)第0到63字節(jié))必須存儲(chǔ)到第0組,第1條緩存線存儲(chǔ)到第1組,以此類推。每一組有8個(gè)單元可用于存儲(chǔ)它所關(guān)聯(lián)的緩存線,從而形成一個(gè)8路關(guān)聯(lián)的組(8-way associative set)。當(dāng)訪問(wèn)一個(gè)內(nèi)存地址時(shí),地址的第6到11位(譯注:組索引)指出了在4KB內(nèi)存頁(yè)中緩存線的編號(hào),從而決定了即將使用的緩存組。舉例來(lái)說(shuō),物理地址0x800010a0的組索引是000010,所以此地址的內(nèi)容一定是在第2組中緩存的。
但是還有一個(gè)問(wèn)題,就是要找出一組中哪個(gè)單元包含了想要的信息,如果有的話。這就到了緩存目錄登場(chǎng)的時(shí)刻。每一個(gè)緩存線都被其對(duì)應(yīng)的目錄單元做了標(biāo)記(tag);這個(gè)標(biāo)記就是一個(gè)簡(jiǎn)單的內(nèi)存頁(yè)編號(hào),指出緩存線來(lái)自于哪一頁(yè)。由于處理器可以尋址64GB的物理RAM,所以總共有64GB/4KB == 224個(gè)內(nèi)存頁(yè),需要24位來(lái)保存標(biāo)記。前例中的物理地址0x800010a0對(duì)應(yīng)的頁(yè)號(hào)為524289。下面是故事的后一半:
?
2、在組中搜索匹配標(biāo)記
由于我們只需要去查看某一組中的8路,所以查找匹配標(biāo)記是非常迅速的;事實(shí)上,從電學(xué)角度講,所有的標(biāo)記是同時(shí)進(jìn)行比對(duì)的,我用箭頭來(lái)表示這一點(diǎn)。如果此時(shí)正好有一條具有匹配標(biāo)簽的有效緩存線,我們就獲得一次緩存命中(cache hit)。否則,這個(gè)請(qǐng)求就會(huì)被轉(zhuǎn)發(fā)的L2 cache,如果還沒(méi)匹配上就再轉(zhuǎn)發(fā)給主系統(tǒng)內(nèi)存。通過(guò)應(yīng)用各種調(diào)節(jié)尺寸和容量的技術(shù),Intel給CPU配置了較大的L2 cache,但其基本的設(shè)計(jì)都是相同的。比如,你可以將原先的緩存增加8路而獲得一個(gè)64KB的緩存;再將組數(shù)增加到4096,每路可以存儲(chǔ)256kb。經(jīng)過(guò)這兩次修改,就得到了一個(gè)4MB的L2 cache。在此情況下,需要18位來(lái)保存標(biāo)記,12位保存組索引;緩存所使用的物理內(nèi)存頁(yè)的大小與其一路的大小相等。(譯注:有4096組,就需要lg(4096)==12位的組索引,緩存線依然是64字節(jié),所以一路有4096*64B==256KB字節(jié);在L2 cache眼中,內(nèi)存被分割為許多256KB的塊,所以需要lg(64GB/256KB)==18位來(lái)保存標(biāo)記。)
如果有一組已經(jīng)被放滿了,那么在另一條緩存線被存儲(chǔ)進(jìn)來(lái)之前,已有的某一條則必須被騰空(evict)。為了避免這種情況,對(duì)運(yùn)算速度要求較高的程序就要嘗試仔細(xì)組織它的數(shù)據(jù),使得內(nèi)存訪問(wèn)均勻的分布在已有的緩存線上。舉例來(lái)說(shuō),假設(shè)程序中有一個(gè)數(shù)組,元素的大小是512字節(jié),其中一些對(duì)象在內(nèi)存中相距4KB。這些對(duì)象的各個(gè)字段都落在同一緩存線上,并競(jìng)爭(zhēng)同一緩存組。如果程序頻繁的訪問(wèn)一個(gè)給定的字段(比如,通過(guò)虛函數(shù)表vtable調(diào)用虛函數(shù)),那么這個(gè)組看起來(lái)就好像一直是被填滿的,緩存開(kāi)始變得毫無(wú)意義,因?yàn)榫彺婢€一直在重復(fù)著騰空與重新載入的步驟。在我們的例子中,由于組數(shù)的限制,L1 cache僅能保存8個(gè)這類對(duì)象的虛函數(shù)表。這就是組相聯(lián)策略的折中所付出的代價(jià):即使在整體緩存的使用率并不高的情況下,由于組沖突,我們還是會(huì)遇到緩存缺失的情況。然而,鑒于計(jì)算機(jī)中各個(gè)存儲(chǔ)層次的相對(duì)速度,不管怎么說(shuō),大部分的應(yīng)用程序并不必為此而擔(dān)心。
一個(gè)內(nèi)存訪問(wèn)經(jīng)常由一個(gè)線性(或虛擬)地址發(fā)起,所以L1 cache需要依賴分頁(yè)單元(paging unit)來(lái)求出物理內(nèi)存頁(yè)的地址,以便用于緩存標(biāo)記。與此相反,組索引來(lái)自于線性地址的低位,所以不需要轉(zhuǎn)換就可以使用了(在我們的例子中為第6到11位)。因此L1 cache是物理標(biāo)記但虛擬索引的(physically tagged but virtually indexed),從而幫助CPU進(jìn)行并行的查找操作。因?yàn)長(zhǎng)1 cache的一路絕不會(huì)比MMU的一頁(yè)還大,所以可以保證一個(gè)給定的物理地址位置總是關(guān)聯(lián)到同一組,即使組索引是虛擬的。在另一方面L2 cache必須是物理標(biāo)記和物理索引的,因?yàn)樗囊宦繁萂MU的一頁(yè)要大。但是,當(dāng)一個(gè)請(qǐng)求到達(dá)L2 cache時(shí),物理地址已經(jīng)被L1 cache準(zhǔn)備(resolved)完畢了,所以L2 cache會(huì)工作得很好。
最后,目錄單元還存儲(chǔ)了對(duì)應(yīng)緩存線的狀態(tài)(state)。在L1代碼緩存中的一條緩存線要么是無(wú)效的(invalid)要么是共享的(shared,意思是有效的,真的J)。在L1數(shù)據(jù)緩存和L2緩存中,一條緩存線可以為4個(gè)MESI狀態(tài)之一:被修改的(modified),獨(dú)占的(exclusive),共享的(shared),無(wú)效的(invalid)。Intel緩存是包容式的(inclusive):L1緩存的內(nèi)容會(huì)被復(fù)制到L2緩存中。
總結(jié)
內(nèi)存層次結(jié)構(gòu)的意義在于利用引用的空間局部性和時(shí)間局部性原理,將經(jīng)常被訪問(wèn)的數(shù)據(jù)放到快速的存儲(chǔ)器中,而將不經(jīng)常訪問(wèn)的數(shù)據(jù)留在較慢的存儲(chǔ)器中。
一般情況下,除了寄存器和L1緩存可以操作指定字長(zhǎng)的數(shù)據(jù),下層的內(nèi)存子系統(tǒng)就不會(huì)再使用這么小的單位了,而是直接移動(dòng)數(shù)據(jù)塊,比如以緩存線為單位訪問(wèn)數(shù)據(jù)。
對(duì)于組沖突,可以這么理解:與上文相似,假設(shè)一個(gè)緩存,由512條緩存線組成,每條線64字節(jié),容量32KB。
假如它是直接映射緩存,由于它往往使用地址的低位直接映射緩存線編號(hào),所以所有的32K倍數(shù)的地址(32K,64K,96K等)都會(huì)映射到同一條線上(即第0線)。假如程序的內(nèi)存組織不當(dāng),交替的去訪問(wèn)布置在這些地址的數(shù)據(jù),則會(huì)導(dǎo)致沖突。從外表看來(lái)就好像緩存只有1條線了,盡管其他緩存線一直是空閑著的。
如果是全相聯(lián)緩存,那么每條緩存線都是獨(dú)立的,可以對(duì)應(yīng)于內(nèi)存中的任意緩存線。只有當(dāng)所有的512條緩存線都被占滿后才會(huì)出現(xiàn)沖突。
組相聯(lián)是前兩者的折中,每一路中的緩存線采用直接映射方式,而在路與路之間,緩存控制器使用全相聯(lián)映射算法,決定選擇一組中的哪一條線。
如果是2路組相聯(lián)緩存,那么這512條緩存線就被分為了2路,每路256條線,一路16KB。此時(shí)所有為16K整數(shù)倍的地址(16K,32K,48K等)都會(huì)映射到第0線,但由于2路是關(guān)聯(lián)的,所以可以同時(shí)有2個(gè)這種地址的內(nèi)容被緩存,不會(huì)發(fā)生沖突。當(dāng)然了,如果要訪問(wèn)第三個(gè)這種地址,還是要先騰空已有的一條才行。所以極端情況下,從外表看來(lái)就好像緩存只有2條線了,盡管其他緩存線一直是空閑著的。
如果是8路組相聯(lián)緩存(與文中示例相同),那么這512條緩存線就被分為了8路,每路64條線,一路4KB。所以如果數(shù)組中元素地址是4K對(duì)齊的,并且程序交替的訪問(wèn)這些元素,就會(huì)出現(xiàn)組沖突。從外表看來(lái)就好像緩存只有8條線了,盡管其他緩存線一直是空閑著的。
根據(jù)內(nèi)存計(jì)算cacde結(jié)構(gòu):
page是os的概念,而cache是cpu的概念。虛擬地址和物理地址以page為單位進(jìn)行操作的,由兩部分組成:page地址和page內(nèi)地址(偏移),所以,os的page和cpu的cache是沒(méi)任何必然關(guān)系的。
審核編輯:湯梓紅
評(píng)論
查看更多