深入淺談,CPU設計原理

 

首先,聲明這是一篇轉載文,這篇文章是,從卡飯論壇

看到的一篇文章《深入淺談,CPU設計原理》,是一篇連載,文章,卡飯論壇,是我高中的時候,經常去的論壇,裏面有很多好的文章,推薦給大家。也許同學亦或在硬件吧或顯卡吧,看過此文的連載。

進入今天的主題,這篇文章《深入淺談,CPU設計原理》,作者是跳海自殺的死魚,當時他寫的時候,是在2009年,而我看到的時候是2011年,當時我很迷硬件,對這種原理性的東西很有渴望感,於是我看這篇文章的時候,還是邊做筆記邊看的,當時每天只能看個2-3頁,筆記記了我還記了幾張紙,後來實在受不了,就把它打印下來看。

介紹下作者,如果沒有查錯的話,作者當時寫這篇文章的時候是剛讀高2的時候!

你們應該知道我想說什麼了,NB的人總是存在的,想想我讀高二的時候.........出走

PS..當時我記得留言的有一個是同濟大學軟件學院的,一個是交大軟院

有時候,好的文章,就像這篇文章,看到它的人不會超過20人,但是它確實對我這種人有用。

如今,這篇文章在某些角度看來不值一提,這些內容也許都在《計算機組成原理》講過了,可是課本上的知識或許不夠,亦或者淺顯,多去讀一些文章,也許會更好。

有時候,我們不必刻意要求幹一件事情需要特別的理由,我覺得只要一件事是有意義的事,就值得去幹。

以後,我會寫一些我認爲值得分享的文章

上正文:

                                                           深入淺談,CPU設計原理

跳海自殺的死魚

序言
也許很多朋友都會對CPU的原理比較感興趣,從網上大家可以蒐集到“利用晶體管的開關閉合進行二進制運算或數據存貯”之類的東西,但是CPU的性能由何決定?CPU內部的設計師如何的?分支預測,亂序執行到底是什麼?超長流水線,RISC又是何物?爲什麼酷睿的CPU頻率低,但是比高頻率的P4性能好那麼多?這些我們應用中遇見的問題絕對不是幾句簡單的話語可以解釋的。
寫本文的目的,就是想讓大家對CPU的瞭解更加深刻一些,更好的理解CPU到底是怎麼運轉的。但同樣,考慮到僅僅是普及文,我個人不會對一些特別的技術和實現方式(比如邏輯電路的設計,編譯原理等等)進行詳細講解,我們只需要知道這些東西通過一些設計就可以達到預期的效果,具體問題是工程師的問題。我會盡量讓文章做到範圍廣,深度淺,把晦澀難懂的知識儘量具體化形象化,但不得不承認我也只是一個普通愛好者,所以錯誤也是難免的,希望大家共同交流,相互促進。
這玩意實在太長了,不可能一帖,甚至幾帖就可以說完,所以我決定利用連載的方式來寫這篇文章。
內容大體分爲三大章
1 處理器的數據線路和控制實現
2 流水線及其帶來的問題
3 存儲器結構的討論

系統學習是比較繁瑣的,但是想要了解一些東西必須要把基礎打好,希望有心的朋友仔細讀下去,我相信也許你會愛上計算機。

第一章

1.1 指令的執行過程(這裏沒有涉及到高級語言的編譯,所以沒有翻譯指令)
CPU的性能由哪些因素決定?我想大多數人都會知道時鐘週期這個概念,而另兩條“指令數目”,“指令所需時鐘週期數(CPI)”是並不爲人所知的。而不同的內部架構,也便深刻的影響了CPI的大小,所以有些處理器頻率低但是處理任務更快。不過別急,一口喫一個胖子是不行的,在我們對CPU的架構進行探討之前,先來看看CPU是怎麼處理一條指令的。
一條指令,首先會根據程序計數器(記錄當前運行的指令地址,本身也是一種寄存器)從內存中去處指令,通過指令字段的內容,選擇讀取一個或者兩個寄存器(在彙編語言中,一條代碼,即一個指令只能使用1或2個寄存器進行加減乘除運算,而寄存器,就是記錄數據的一個東西,當然他還有更多用途,之後我們會提及)一旦取得寄存器的操作數後,就可以對指令進行定性,大致分爲三種,存儲訪問,算術邏輯和分支(分支也可以叫做跳轉,一般的高級語言都用if語句,判斷一些特定條件從而進行兩種或者更多的操作)存儲訪問指令需要對存儲單元進行讀出或者寫入而訪問寄存器;算術邏輯指令需要將ALU(運算器)計算得到的數據寫回寄存器;而分支指令則會通過對數據的比較,決定是否對下條指令地址進行修改(也就是是否進行if語句中的or後面的語句)

話外音:一條高級語言首先會被編譯爲彙編語言,之後通過指令集將其編譯爲二進制信號,從而讓CPU明白它需要做哪些操作。
不要將緩存和寄存器的概念混淆,最好從百度百科上看看相應資料。緩存我們會在第三大章進行詳細討論。

1.2 數據通路的建立

什麼叫數據通路?我想聰明的你一定知道,讓一條指令順暢執行下去,這就是一條數據通路,它包括指令,數據存儲器,寄存器堆,運算單元和加法器(加法器是對程序計數器進行調節的,也就是說當前指令執行完畢之後,加法器對程序計數器加上4,就可以切換到下一個指令地址,關於爲什麼加4這裏不多說,在第三大章我們會提及)。
想要構成數據通路,首先需要一個存儲程序指令的地方,那麼我們就需要一個存儲單元來存儲程序指令,並根據所給地址提供指令。當前需要執行的指令一定要放在一個存儲單元中,這就是我們之前提到的程序計數器(PC),然後同一個加法器增加PC的值使它指向下一條指令的地址。
得到了指令地址,就可以讓ALU對指令進行處理,從而得到結果,而ALU想要進行計算,就需要從寄存器獲得數據並寫入寄存器,這樣,我們就需要一個寄存器堆,來暫時存儲這些指令地址,信息等等。
如果是儲存訪問指令和算術指令的話,那麼接下來只需要將寄存器的數據顯示,或者暫時儲存就可以了,若是分支指令,就需要對ALU的數據和寄存器的數據進行比較,如果爲真,則執行if語句後緊跟的語句,如果爲假,就會執行if語句中or後緊跟的語句,所以說分支指令總是延遲的,因爲在沒有得出指令的真假時我們不能對接下來的指令進行處理。

1.3 指令的處理實現
一條指令,通過拆分(解碼器就是對指令進行拆分,重排等操作的,使用邏輯電路從而達到了一種特殊算法),因爲ALU的處理能力是一定的,所以有些複雜指令不能在一個週期內處理完,而需要拆分或者讓ALU利用更長的一個週期重複處理這段指令。
對於單週期實現,和1.2節中的數據通路非常相近,如果有不熟悉的朋友可以回閱上一節。
多週期的數據通路,與單週期通路最本質的區別有三點:
1 指令和數據使用相同的儲存單元
2 只有一個ALU(也可以使多個,但目前不對其進行討論),沒有了加法器
3 每個重要的功能單元都加上了一些寄存器存儲輸出值,使後面的時鐘週期得到需要的信息(這是寄存器的另一個用處)。
當一個時鐘週期結束後,我們只需要將處理得到的結果反饋給PC程序計數器,那麼它就可以再次發射指令,所以就沒有必要安置加法器。
說了這麼多,相信有心的朋友們就會發現,其實我們所說的單週期指令實現,就是早期的CISC複雜指令集計算機,而多週期就是RISC精簡指令集計算機。而CISC這個東東有兩個致命缺點,第一,它不能夠很好的進行流水操作(第二大章的內容,以後詳細講解),第二,因爲指令的複雜程度不一,那麼如果爲了性能而使用可變時鐘週期的設計,會大大增加控制器等其他單元的設計問題,而若是使用固定的時鐘頻率,又會造成極大的浪費,所以CISC現在已經基本被拋棄了。


1.4 異常
在CPU設計中,最具有挑戰性的一個問題,是一個程序異常(打斷程序運行,比如錯誤的保存了指令的結果),被中斷(來自處理器外的異常,比如內存的讀寫錯誤),就比如算術溢出。很多業內人士並不區分開兩者,都稱之爲中斷。

那這些異常是如何處理的呢?爲了對其進行處理,我們必須知道是那些指令引起的異常問題,目前有兩種方法,第一種需要一個狀態寄存器,其中有一個字段用於記錄異常產生的原因。另一種方法利用向量中斷(用來控制轉換的終端地址)這兩種方式在這裏我們不去展開細講,更多的是爲了下面幾章做一點點鋪墊。

1.5 實例 奔騰處理器的內部架構
奔騰系列都是採用流水線設計,它讓多條指令重疊從而達到更高的指令吞吐率,其時鐘週期的長度有單個功能單元的延遲決定。這裏我們暫且放一放,之後還會有詳解。
Intel的IA 32指令集(指CPU能識別什麼類型的指令)非常複雜,是對其實現控制電路的一大難點,雖然核心內容都是源於前幾節的內容,但是想要出成品並不容易
IA 32的指令,有些可能用到幾十個週期,甚至超過幾百個週期的指令。例如,串行傳送指令要求計算並修改兩個不同的存儲地址,並且存儲一個字節串。而且他複雜的尋址模式也使其使其結構實現難度大大提高。
而Intel的工程師巧妙地運用多週期數據通路和微程序控制器(使用代碼而不是01來表示控制的方法),這樣,即使是需要週期數不同的指令,也會減少更多的週期損失(因爲頻率是固定的)

1.6 奔騰4的結構
超標量,這是在奔騰4系列CPU中引入的一種技術,簡單來看,這種技術使得處理器可以有多條數據通路,每一條處理某一類型指令:存取指令,ALU計算,分支。這樣處理器便可以在一個週期內執行多條指令(當然這些都僅僅是皮毛,之後我們依舊會在第二大章進行討論)。而所謂的微操作,便是利用某種技術,讓每一條指令分配給不同的數據通路,從而達到更高的效率。
在奔騰4中,蹤跡緩存技術就是來存儲微指令的,這是一種解決方案,記錄微指令將會被引入哪一條特定的數據通路。這種緩存技術比較複雜,我們會在第三大章中稍作了解。
奔騰4使用簡單的硬聯線控制和簡單數據通路,結合蹤跡緩存,獲得了令人喫驚的時鐘頻率,當然還要得益於深度流水線的引入,不過這都是後話了。

第二章 流水線,更高更快

2.1 流水線初涉
相信對於大多數了解硬件的朋友,流水線早已不是什麼陌生的詞彙了,它並不難理解。
我們先回顧一下一般的數據通路如何處理指令,首先PC會將指令地址送給ALU,ALU進行處理將數據存儲,然後PC值加4 ,這樣就可以進行下一個指令的處理。而流水線便是不間斷的發射指令,原本一個指令需要得到結果纔會被處理,而現在則是讓指令充斥着整個數據通路,在ALU處理第一條指令時,第二條指令已經進行取值,當第一條指令處理後,第二條指令立刻被送往ALU,這樣就減少了非常多的時鐘週期(一個指令需要經過取指令,訪問寄存器,ALU操作,訪問數據,訪問寄存器這五步)(再次進行解釋,第二步中的訪問寄存器是讓ALU得到操作數據,第四步訪問數據也就是得到ALU的計算結果所必須的一些數據,例如a+b,我們需要知道a和b的值是多少。而訪問寄存器就是對PC的操作)如果這樣說還不明白的話,我們簡單打個比方,我們做飯,需要先把菜做好,然後去煮米飯,而流水線話操作時,我們可以一邊煮米飯,一邊做菜,這樣就減少了整個做飯的時間。而假設煮米飯要10分鐘,做菜要20分鐘,那麼整個流水化操作後就需要20分鐘,也就是說流水線操作的時間取決於需要時間最長的事情,在CPU中就是任務的執行週期數取決於最複雜,處理速度最慢的指令(比如一個超長浮點運算)

附言:流水線增加的是CPU指令的吞吐率,而不是減少了單個指令執行的時間,而且因爲某些特殊的問題,還會讓單個指令執行時間增加,例如流水線寄存器的引入



2.2 流水線結構的冒險
我想當看完2.1節後大家一定會說,流水線竟然如此簡單?確實流水線並不難理解,但是真正實現過程中人們發現了相當多的問題,會引起流水線的處理停頓,我們稱爲冒險。

結構冒險
這是第一種冒險,即硬件不支持多條指令在同一個時鐘週期內執行。比如我們做菜的時候,因爲家裏的電器因爲線路的原因只能工作一個,那麼我們只能先煮米飯,再做菜了。
如果用CPU角度來考慮的話,如果在流水線中一個指令在該週期內需要從內存中取得數據,而同時另一條指令需要寫入內存,這樣,因爲DDR內存在一個週期內只能執行一次讀或者寫操作(數據總線只有一條,而對於GDDR3之後的顯示卡內存,因爲具備多條數據總線,可以對內存同時進行讀寫操作)那麼這兩條指令就造成了結構冒險。

數據冒險
這是第二種冒險,在一個操作必須等待另一操作完成後才能進行時,那麼就會造成流水線停頓。例如我們需要知道a和b,需要將兩個數相加得到c的值,之後再對c進行立即數操作(直接對數值進行加減乘除運算,而不需要讀取被加數的寄存器地址),那麼必須等a和b相加後得到的數據寫到寄存器中才可以進行下一個操作。

控制冒險
第三種冒險,當處理器需要根據一條指令的結果作出決策,此時其他的指令可能在執行中。好比一個分支操作,我們需要知道是執行分支中哪一條指令才能進行下一步操作,這樣接下來的指令就需要得到分支的結果才能進行處理。



2.3 有問題就要解決
既然流水線有那麼多限制,爲何不去簡化它呢?或者說直接不流水。可是經過很多科學家的驗證,流水線對程序提升的性能即使存在如此多的冒險,依舊客觀,根據流水線加速比的公式,一個5步驟處理的CPU可以提升3~4倍的性能(實則5倍,但是因爲有冒險)
不去解決流水線的冒險可以嗎?對於像GPU這種高度並行,數據之間關聯度低(也就是流水線的冒險度較低,不易發生流水線停頓)我們完全沒有必要去理會,不過通過對指令並行的提升(ILP Instruction-Level-Parallelism )比如co-issue(相信關注GPU核心的朋友對這個詞都會有所瞭解),讓兩個完全沒有關聯的數據進行流水操作從而提升性能,當然就目前來看ILP已經走到了盡頭。
而CPU則不然,因爲我們運行的程序關聯性較大,如果依舊無解決辦法直接流水,那麼損失的時鐘週期是相當恐怖的,那麼我們就來簡單談談CPU設計中是如何減少這種冒險的。

數據旁路:這是一種解決數據冒險的方法,它使用內部的數據緩存直接提供缺少的數據,而不需等待該數據到達程序員可見的寄存器或內存才使用。實現它,需要用到直通技術。
爲了直觀,我們先舉一個簡單的例子,a+b=c,c+d=f,我們知道了a,b,d的值,要得到c的數據才能相加。首先我們的CPU會先計算出c的值,這時候利用直通技術,將c的值直接傳給下一個指令,而不需要等到該指令結束後由PC將地址發送,那麼下一條指令也便無需等待,直接可以計算。

重排指令:這裏我們需要用一個簡單的例子,我們需要計算a,b,而a=c+d,b=c+e,需要的數據都在寄存器上,因爲要流水,那麼就要重複獲取c的值,如果對這段高級語言進行編譯,那麼因爲c的值需要在一個週期內取得兩次(在正常編譯後是這種情況),所以就會發生冒險。如果我們對加載c值的順序進行調整,就可以避免衝突。這也可以叫做亂序執行,也就是out of order

分支預測與分支延遲:這都是用來解決控制冒險的,因爲需要得到分支的結果纔可以進行下一步,那麼在一些流水級超長的CPU中(比如奔騰4)會造成相當大的週期損失。分支預測,很簡單,就是通過某種方式對分支的結果進行預測,那麼我們也便無需等待分支的結果是真是假,直接執行下一步便可。分支延遲也很簡單,我們先不理會分支的結果如何,對分支的兩種可能都進行運算,等到分支得到結果之後再捨棄。
目前流行的都是分支預測,這裏拆開還能將非常多,比如BTB,我們會在下幾節進行深入探討




2.4 流水線的數據通路
首先,作爲進階的思考,我們還是回顧一下一般的單週期數據通路,我們經典的5階段的CPU,分爲IF取指令,ID指令譯碼(編譯),EX指令執行,MEM數據內存訪問,WB寫回。如果用5階段的CPU進行流水,那麼任意週期內,都會有一條指令充滿這5個階段,那麼爲了讓指令能夠得到不間斷的發射,我們必須增加更多的寄存器,當一條指令經過一個階段後馬上讓PC值加上4,從而發射下一條指令,所以我們採取的方式是,在每個階段之間都要加上流水線寄存器,比如在IF和ID段中加入一個寄存器,那麼該寄存器的作用便是得到IF的指令值,反饋,使得PC值加4,而且它還要做指令數據傳輸的工作,也就是將IF的數值傳送給ID段。那麼流水線寄存器的頻率就一定要是流水線本身頻率的兩倍,所以在一個週期中,前半段時間讓PC值加上4,後半段則是傳送數據給下一個階段。


附錄:superpipeline 超長流水線。
超長流水線,也稱深度流水線,它是一種高級流水線技術,在這裏我們簡單來探討一下。

首先要指出的是,超長流水線並不是指ALU更多的流水線。它的含義是將一個較短階段(stage)的流水線劃分成更多階段。
其實在我一開始接觸的時候也不明白,爲什麼劃分更多階段能給予良好的流水性能呢?其實這一點不難理解,因爲這樣,在一個週期內流水線內部的指令數會比原來更多,同樣因爲劃分的更加細,頻率也更容易提升(因爲流水線的頻率取決於速度最慢的那一階段),這樣,在計算大量數據時,這種超長流水線就可以得到很好的加速效果,但是,若是出現冒險,那麼損失的時鐘週期是非常可怕的。P4當中引入了大量的緩存(cache),目的就是讓指令所需的數據能在cache中得到而不去等待慢吞吞的內存(目前內存的頻率都是200,DDR運用了DIMM上下沿併發和數據預取的技術從而等效達到了高頻率,但是一個1.8G的CPU依舊需要9個週期去等待內存數據)P4能達到較高的頻率,也很大程度上依靠了這種高級流水線技術。


高級話題:性能的再次提升

流水線的開發,同樣造就了一個概念,叫做指令集並行,。主要通過兩種方式提高指令集的並行能力一個是增加流水線技術,另一種則是設計更多的內部元件從而在一個流水線中的每級發射更多的指令。前者我們已經有所探討,他就是P4中引入的superpipeline。後者就包括超標量設計,涉及到了流水線的寬度。

我們現在深入探討第二個話題。這種多發射技術,目前有兩種比較聰明的實現方法,他們的區別就是編譯器和硬件之間如何分工,由於分工主要在於某些決定是在編譯期決定的還是在運行期決定的,所以前者稱之爲靜態多發射,後者叫做動態多發射。

靜態多發射處理器利用編譯期從而對指令進行打包和處理各種特殊的流水線冒險,在靜態多發射中,我們可以將多條指令看作一條超長指令,複雜指令。同樣,因爲本身可以看作一條超長指令,那麼它便被固定了長度,操作數。這就是所謂的VLIW,超長指令字,相信對R600瞭解的朋友一定不會陌生。

動態多發射處理器也被稱之爲超標量處理器,在比較簡單的模型中,指令是按照順序發射,每個週期可以發射1條或多條指令,或者說不發射。非常明顯的是,這種處理器要達到較好的性能非常依靠編譯期對各條指令的調節,錯開指令之間的依賴關係從而達到更高的發射速率。所以目前超標量處理器會更多的在指令排序方面下工夫。

附錄:多發射處理器的數據通路
非常簡單,我們只需要在一般的多週期數據通路上增加更多的執行單元,比如ALU,對各種單元進行劃分,比如A單元執行浮點,B單元執行整數,通過對寄存器的大小調節,便可以適應不同的指令。

第二章,完

第三章 層次結構存儲器

1 導論
假如你是在做高數作業,爲了快速計算,你會把積分表放在最顯眼的地方,其次爲了防止對一些積分變換的不熟練,你會放一個例題本在一旁,這樣又不會的變換就可以直接查。而如果有一個題你怎麼也做不出來,就值得去求教老師。

其實本來作業應該是我們自己做的,實在不會就去請教老師,但是藉助了積分表和積分變換的例題就能更快的解決題目,而不需要去請教老師,因爲請教老師太麻煩太慢了。這樣,在CPU當中也存在這種類似的環境,也就是說最能幫助你解決當前問題(指令最需要的數據)的應該優先拜訪(優先訪問),再或者說你有兩本詞典,一本是英語5000實用詞,另一本是牛津高階英漢詞典,查單詞的時候,我們當然會先去查看實用詞詞典咯,大辭典查起來太慢了。

下面就得引入必備的理論知識了

時間局部性:如果一個數據項被引用,那在不久之後它很有可能再次使用。比如在第一個例子中,比如一個循環指令,每次循環都需要一個常量B,那麼B就非常容易被調用

空間局部性:如果某個數據項被引用,那麼不久之後與它地址相近的數據項可能會被引用,也就是說數據間有相依性啦

層次結構存儲器,也就是按照這兩種原理所衍生的。現在的計算機就有緩存和內存之分(寄存器不屬於層次結構存儲器,它的目的是存儲目前需要使用的數據,而不是可能會使用到的數據,切記切記),緩存小而快速,一般都在CPU的uncore部分,內存就很容易理解了,在緩存和硬盤之間的,速度和容量在兩者之間其緩衝的東東。爲什麼速度快容量就要小呢?這是一個邏輯電路的知識,簡單的來說就是爲了得到更高的速度,那信號的發射頻率必須提升,而爲了得到更大的容量,那麼就必須保證信號傳輸中減少干擾。如果頻率提高,干擾就會增強,所以兩者比較難以統一。


存儲器的層次結構可以由多級組成,但是數據複製每次必須在兩個相鄰層次間纔可以進行,一個層次結構中存儲信息的最小單元,我們稱之爲快或行(line,cache line,眼熟吧)如果處理器需要的數據出現在高層(速度快,容量小的cache)的塊中,測稱之爲一次命中,而命中率就是指我們需要的數據在cache中所佔的比例。缺失率也就是cache中沒有的數據佔需求數據的百分比。


1.2 緩存的簡單基礎知識
cache,這是專業的叫法,其實就是一種層次結構罷了,早期的緩存都屬於片外緩存,也就是集成在主板上的,現在的緩存都會放到CPU的Uncore中,也就是非核心模塊,cache本身屬於一種超高速內存(可以如此理解),但是本身卻和內存有較大的差距,因爲cache屬於高速量小的存儲器,那麼如何合理利用這分寶貴的資源就是人們必須要掌握的了。

我們先來討論一個比較簡單的情況,假設內存中存儲了1到10十個數據,如果我們這時候讀取其中的4號數據,那麼cache將會立刻將3,4,5這些數據調入,因爲根據之前我們所講的時間空間局部性,這些臨近的數據和數據本身都是很有可能被再次用到(數據第一次讀取是去內存找而不是緩存,術語叫做強制性衝突,下一次會有講到哦)再來說一個,如果cache中有1~10數據中的3,4,5,如果CPU需要6號數據,而cache中沒有,那麼5,6,7數據就會立即調入cache,根據局部性原理。

這時候追求細節的朋友們可能會提問,CPU怎麼知道cache中有沒有所需的數據呢?如果在,又是如何找到的呢?最簡單的方法就是給內存的每一個數據(字)分配一個緩存的地址,每一個內存的數據地址對應一個確定的緩存地址(數據重複),也就是說,當我們讀取內存發現cache中含有該數據那麼就會去cache中尋找,從而縮短的很多時間。這種簡單的分配方式叫做直接映像。由於每個緩存的地址可對應的內存中多個不同地址,怎麼才能知道所請求的數據是否在緩存中呢?這就得引入標記這個概念了,標記必須包含能夠判斷緩存中的數據是否爲所請求數據的地址信息。我們還需要一種辦法來判斷緩存中的確沒有有效的信息,我們就必須知道緩存中部分標記要被忽略,最常用的方法就是有效位,說明一個cache塊中是否有有效地址。如果這個位置沒有設置,就不能讀取其內容。


1.3高速緩存的缺失
在接下來討論實際系統的高速緩存之前,我們現在看一看控制部件是如何處理cache miss的。首先呢,我們必須通過控制器檢測到缺失的發生,然後從低一級的緩存或內存中取出相應的數據,如果高速緩存命中,CPU繼續照常處理,就好像什麼事情都沒有發生。
命中時,處理器的控制方法的修改並不太重要,而缺失時就必須附加一些工作了。cache的缺失處理由兩部分協同完成,一個是處理器的控制器,另一個就是進行初始化內存訪問和重新填充緩存的獨立緩存控制器。高速緩存的缺失導致流水線停頓(去看第二章),與流水線中斷不同(比如你運行QQ,結果突然間把它關掉了),中斷髮生時,系統要求保存所有寄存器的狀態,而缺失發生時,我們在等待主存操作完成的期間,可以使整個機器停頓,本質上就是凍結臨時寄存器和程序員可見的寄存器(學編程吧,否則我講了也不會明白)的內容,而緩存的缺失只是流水線停頓的一種情況,所以也是非常簡單的。
作爲硬件討論的基礎我們依然要討論一下對於多週期或流水線的數據通路(進階,必須把第一章的看完),指令發生缺失時將如何處理?如果去一條指令導致缺失。那麼指令寄存器的內容無效。爲了把正確的指令取到高速緩存中,必須讓存儲器執行一次讀操作,由於是在流水線中,在指令執行的第一個週期,程序計數器加一個增量,因此產生高速緩存指令缺失的指令地址等於程序計數器減4.一旦有了地址,就可以只是主存執行一次讀操作,之後等待存儲器的相應,然後把數據寫入高速緩存。
說了這麼多估計大家都已經暈掉了,下面我們再來條理地回顧一下高速緩存去指令缺失的處理步驟
1 把最初PC的值送到存儲器
2 指示主存執行讀操作
3 寫高速緩存,把從存儲器中讀到的數據寫入高速緩存的數據部分,把地址的高位寫入標記域,設置有效位
4 重啓第一步,它將重新取指令,這次發現指令在高速緩存中。

1.4 寫操作
寫操作稍微有些不同。假設有一個存儲指令,我們只把數據寫入高速緩存,那麼寫入高速緩存後,主存和高速緩存相應位置的值將會變化。在這種情況下,高速緩存和主存稱爲不一致。保持一致的最簡單方法就是在緩存和主存中都寫入數據,也就是寫通過(write through)。
儘管這種方法十分簡單,但是並不能提供良好的性能,所以,寫緩衝(write buffer)就出現了。當一個數據在等待被寫入主存時,先把它存放在緩衝器中。在把數據寫入高速緩存和寫緩衝後,處理器繼續執行指令。當寫入主存操作結束後,寫緩衝裏的內容也將被釋放。如果寫緩衝滿了,那麼當處理器執行到一個寫操作時,就得先停下來知道寫緩衝有一個空的位置。當然,如果存儲器完成寫操作的速度比處理器的速度慢,怎麼緩衝也沒有用。
寫通過機制還有一種變通的方法是寫回(write back),採用寫回的方法,當一個寫操作產生時,新的數據僅僅被寫道高速緩存的塊中,只有當修改過的塊被替換時,才把它寫到底層的存儲結構中去。

附言:其實還有很多很多操作方式,我累了。。。。。只是說了說常見的。。。。。。

11月8日發燒,帶病堅持= =低調
(實在不忍心繼續停下了,順便打算給圖形學簡單開個頭,希望大家捧個場)


1.5 塊的定位方式與cache缺失

一直到現在,我們討論的將塊放入cache中,都是用最簡單的定位方式,即一個塊只能放到高速緩存中的一個位置,也就是直接映射(因爲存儲器中任何一個塊的地址都被直接映射到更高層的cache中),事實上強大的工程師們還有非常多的方式來安排塊,直接映射屬於一種極端,一個塊就是直接映射,這時一個塊被精確的放在一個位置中。
另一種極端方式則是,一個塊可以放在cache中的任何位置。這種方式就是所謂的全相聯(因爲存儲器中的塊可以和cache中的任何一塊聯繫)。在全相聯的cache中要找一個給定的塊,由於該塊可能被放在任何位置,必須檢測高速緩存的所有項。爲了使檢測更加簡潔,它是由與cache中美向相關的一個比較器並行處理完成的,這些比較器增大了晶體管的開銷,因爲全相聯只是用那些塊的數量較少的cache。
介於兩種極端的設計叫做組相聯。在一個組相聯的cache中,每個塊可以被放在固定數量(2個以上)的位置上,每個塊有n個位置可以放的組相聯cache被稱爲n路組相聯cache。。一個n路的組相聯cache有許多組構成,每個組中都有n個塊,存儲器的每個塊由下標域對應到cache中唯一的組,並可以放在此組中的任何一個塊上。因此組相聯映射是把直接映射和全相聯映射結合起來的妥協措施,這樣每個塊可以直接映射到一個組,然後檢測該組中的所有塊是否匹配。
講這麼多有毛用?大家別急,現在我們再次回顧一下之前的東東。
在直接映射中,一個存儲塊的位置是如下給出的:
塊號——cache中塊的個數
而組相聯則是:
塊號——cache中組的個數
由於全相聯中所需要的塊可以放在任何地方,所以要檢測cache中所有塊的所有標記。
而組相聯的塊可以被放在組中的任何一個位置,所以不許檢測該組中所有的塊標記。
直接映射的cache可以看作一個簡單的1路組相聯;每個cache的一個項爲一個塊,形成只有一個塊的組。有n項的全相聯cache可以看作是一個n路的組相聯cache,它只有一個組,組中有m個塊,每一項可以放在該組中的任何一個塊中)
提高關聯度的優點是它可以提升cache的命中率,但是卻增加了命中時間,也加大了比較器的開銷。

備註:我們所說的塊就是cache line
就是這樣,命中率並不僅僅決定於緩存的容量,這是一個非常普遍的錯誤認知,cache相聯度同樣與處理器本身有一定關係。

終章 Pentium P4 與AMD Opteron的存儲器
這一章大概就是CPU設計原理的最後一節了,爲了做一個完美的謝幕,當然要用大家最關心的實際問題來結尾啦!
皓龍和奔騰在新的架構中肯定會對一個重要的細節進行修補,那就是我們這一章節詳細討論的cache,兩者都附加有優化措施來提升命中率。首先是在缺失時先返回被請求的數據,如先前小節中所描述的。二者都允許處理器在cache缺失期間,繼續執行訪問數據cache的指令,這一技術被稱爲無阻塞cache,經常被工程師們用在亂序執行(看第二章去)的處理器上,來隱藏cache的缺失延遲。這種技術實現無阻塞有兩個特點,第一就是指令與其他的工作來隱藏一部分缺失延遲,第二就是重疊兩個不同缺失的延遲。
要重疊多重cache缺失的大部分缺失時間需要較高帶寬的存儲系統來並行的處理多重缺失,事實上這一點也被用在GPU中(當任務一在處理時,因爲需要尋找數據所以會有延時,如果在任務一尋找數據的同時執行任務二,那麼這一部分延時就會被隱藏掉,同樣正因如此,GPU才被稱之爲高度並行處理器,另外,這種技術也可以在CUDA編程中應用從而提高GPU的通用計算能力)。
奔4和皓龍的處理器都要預取指令(TLB的一種技術,有些深,沒講的一部分),也都採用內部包含的硬件預取機制來訪問數據。他們觀察數據缺失的模式,並利用此信息預測下一次取指令的信息,在循環訪問時效果很好(循環指令)。
對於微處理器的設計者們面臨的最嚴峻的挑戰之一,就是支持像P4和皓龍這種每個週期可以執行多條存儲器指令的處理器(超標量啊,別告訴我你忘了)。有兩種不同的技術可以支持一級cache中的多個請求。cache可以是多端口的,允許對同一cache塊的多個訪問同時進行。但是多端口cache一般非常昂貴,因爲多端口存儲器中的RAM單元比單端口中的單元大得多(在具體說,單端口的塊不能在同一時間進行多次操作,再多端口中增加了塊的數量)。另外一種方案是把cache分成段,並允許多個互相獨立的存取操作對兩個不同的段進行訪問。
關於細節:AMD和Intel芯片的最大區別也許就是P4指令cache使用了蹤跡cache,而AMD皓龍使用的是更加傳統的指令cache。
不同於順序的將指令裝入一個cache塊中以增加空間局部性,蹤跡cache將找到的動態的包括所有分支的指令裝入一個cache塊。這樣cache塊中包含的就是有CPU決定的執行指令的動態記錄,而不是主存局部決定的靜態指令序列。由於cache中帶有分支預測,我們必須確認分支以及地址的有效性,以保證有效的去指令。另外P4的cache中存的是爲指令而不是皓龍的IA 32指令。
很明顯,蹤跡cache需要更爲複雜的cache地址映射機制,因爲地址已經不再是對齊的了。
然而蹤跡cache可以提高cache塊的利用率。例如,在傳統的cache中,有分支轉跳到另一個很大的塊中,這樣塊的開始一部分佔用的空間就可能不被訪問到。同樣,由分支轉跳可能跳出這樣的塊,那麼塊的最後一部分就很可能被浪費掉了。蹤跡cache僅僅存儲分支入口到離開記錄之間的指令,這樣避免了頭尾的浪費。當然這樣也有一個弊端就是他可能在cache中多次存儲相同的指令,條件分支的不同選擇結構導致了相同的指令成爲了多個蹤跡的一部分,而每一部分都會存儲在cache中。
最後做個總結吧,其實不管是緩存也好,流水線也罷,無非都是通過減少執行時間來提高性能的,目前CPU的趨勢會像GPU一樣發展多核心,如何開發和利用多核心的優勢,減少浪費,這正是未來工程師們研究的啊!

備註:以後我會寫一些我認爲值得的文章,記錄的每一步

我的新浪微博是@酥西黃

我的郵箱是[email protected]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章