第十三章 RISC精简指令计算机
教材參考計算機組織與結構——性能設計(第九版)第14章
這一章我們來講一講一種很著名的計算機架構——RISC(另一種與其相對的叫CISC)。那么問題來了,什么是RISC?
RISC是Reduced Instruction Set Computer,即精簡指令集計算機的簡稱。而與其相對的則是復雜指令集計算機,這里我們不作討論。
RISC之所以叫精簡指令集計算機,就是因為其指令的數目相對較少,我們先來看看它有哪些特征:
-
數量眾多的通用寄存器,可能有數十個至上百個,使用特殊的編譯技術來優化寄存器的使用。
-
指令數量少(從名字也可以看出來吧),格式固定,長度也是固定的。
-
專注于優化指令流水線。
RISC和CISC的對比圖如下:
13.1 指令執行特點
在講RISC的具體特點之前,我們來看一看為什么會出現RISC。
其實,在RISC出現之前,設計者們偏向于CISC,隨著硬件工藝的逐漸成熟,硬件的價格逐漸下降,相對而言提高的則是軟件的價格,同時,高級語言(High Level Language)也在不斷發展,不斷變得復雜。
那么人們就想著,不如讓指令集變得更加多、更加復雜,用更加復雜的硬件實現來滿足高級語言的需要。于是,CISC就旨在簡化編譯器的實現,用更加復雜的指令集來支持高級語言。
但是,之后的人做了一些實驗,讓高級語言的程序跑起來,觀察高級語言的語句在程序中執行的次數,并乘以對應的機器指令的條數,得到了如下的結果:
可以看到出現次數最多的是賦值,即變量的分配問題,而最耗時的是過程調用,同時也要注意分支和循環。總結而言,最應該受到關注的是關于操作、操作數和過程調用的問題。
對于操作數而言,研究發現,多數的變量引用是對局部的標量;而關于過程調用的研究中發現,過程調用需要的參數并不多,98%的過程調用都只需要6個以內的參數,而且過程調用的深度并不深。
這些有關的研究便給了設計者一些啟發:
-
由于操作數大多都是局部變量,于是考慮用更多的寄存器。
-
由于分支、循環、過程調用較多,要更加小心地設計流水線。
于是便產生了RISC這一架構。
13.2 大寄存器組
為什么要用寄存器組呢,這是因為寄存器是最快的一種內部存儲,比cache和內存都要快,而且由于寄存器組中的寄存器數量不多,因此尋址非常快。
但是由于存儲空間不大,那么就要用一些策略來決定哪些最常用的操作數放在寄存器中了。有硬件和軟件兩種方案來解決存儲空間不足的問題。
-
軟件的解決方式:讓編譯器進行分配,需要更加精密地分析程序
-
硬件的解決方式:當然就是多搞幾個寄存器,當然,我們下面介紹另一種策略——寄存器窗口。
什么是寄存器窗口?
寄存器窗口可以理解為由數量有限的幾個寄存器組成的一個小寄存器組。一個寄存器窗口由3個部分組成:
-
參數寄存器
-
局部變量寄存器
-
臨時寄存器
對于過程調用而言,寄存器組總是從一個局部移動到另一個局部,比如A調用了B,那么A的局部變量要被保存起來,然后寄存器給B的局部變量使用。
參數的傳遞是靠重疊的部分來實現的,前一個的寄存器窗口的臨時寄存器和下一個寄存器窗口的參數寄存器是重疊的,這樣就可以不進行數據的移動而進行參數的傳遞。
這樣,過程調用就可以被看作是從一個窗口移動到下一個窗口,而返回就是相反的過程。當然了,一般寄存器窗口不會做成上面那種樣子,而是圓環的形式。
當過程調用發生,當前窗口指針CWP(Current Window Pointer)會被指向當前活躍的寄存器窗口。如果沒有可用的窗口,那么會產生一個中斷,然后最晚返回的窗口會被保存到內存,一個保存窗口指針SWP(Saved Window Pointer)會指向被保存的窗口。如果有N個窗口,那么就可以有N-1個過程調用,這很好理解,比如有2個過程A和B,對應2個寄存器窗口,那么就可以有過程A調用B這一個過程調用。
這時候有人能就會問:局部變量這么處理,那全局變量呢?
典型的處理方式是由編譯器分配到內存。或者也可以用一些全局變量寄存器來存儲,不過這樣會加重寄存器的負擔。
13.3 大寄存器組和緩存對比
大寄存器組和緩存Cache看上去似乎十分相似,貌似大寄存器組也就是起到了緩存的作用,存儲那些經常要用的變量(雖然寄存器要快許多),兩者有什么異同呢?
-
大寄存器組存儲的是所有局部變量,而cache只是最近使用的變量
-
兩者空間的使用都不夠充分,大寄存器組由于給每個窗口分配的容量都是這么多,可能會用不完;而對cache,由于每次都是讀一塊數據,所以一塊中的某些數據實際上都是用不到的。
-
大寄存器組是由編譯器進行分配,而cache是分配給最近使用的局部變量。
-
在和內存交互的時候,大寄存器組是由過程調用的深度來決定的,如果過程調用多了就需要放入內存了,而cache是由替換策略決定的。
-
尋址方式不同
-
大寄存器組一個周期內能夠尋址并取到多個操作數,而cache只能取到一個
下圖展示了大寄存器組得尋址方式,首先指令中的低位會存儲一個虛擬的寄存器號,用來標志尋址的是哪個寄存器,同時,還需要一個窗口號,用來表示是哪一個寄存器窗口。
13.4 基于編譯器的寄存器優化
說完了大寄存器組,我們來講講如何用編譯器對寄存器的分配進行優化,編譯器優化的目的是使得需要的操作數能夠盡量存放在寄存器中,而非在內存中,因為讀內存是十分耗時的。
高級語言是沒有顯式引用寄存器的,比如C語言,雖然C語言有register來聲明寄存器變量。事實上,編譯器是通過映射來分配寄存器的,程序中的每一個變量都會被分配給一個虛擬的寄存器,然后編譯器會將這無限的虛擬寄存器映射到有限的真實寄存器中。多個虛擬寄存器可以共享一個真實的寄存器,如果沒有真實寄存器可用,則會將變量放入內存。
一種編譯器中經常使用的優化方法就是圖染色,圖染色的目的就是找到那些可以共享一個寄存器的變量,然后把他們分配到一個寄存器中,比如下面的A和D都是紅色,則可以被分配給同一個寄存器:
使用圖染色時,首先給每一個可用的真實寄存器分配一個顏色(顏色相同表示分配到同一個寄存器),在這個圖中有結點和邊,結點的含義是每一個要分配的變量(或者說虛擬寄存器),邊則表示組成邊的兩個變量的生命周期是有重疊的,比如A和B的生命周期是有部分重疊的。
過程為:可以采用回溯法的算法思想,比如從A開始著色,給A可以涂上紅藍綠,然后再下一層到B,如果A涂了紅色,那么B只能夠是藍色或者綠色,就分了2個叉(紅色的被剪枝了,因為不滿足約束條件),然后再到C,依次往下。最后可以看到,F是不能夠被涂色的,于是只能分配到內存。
13.5 RISC和CISC對比
講完RISC的寄存器優化之后,我們總結一下RISC的特點:
-
大部分指令在單周期內執行完成
-
操作大多都是寄存器到寄存器的
-
少且簡單的尋址模式
-
少且固定的指令格式
-
硬連線設計(不是微程序)
-
更加有效的流水線
-
對中斷的反應更加迅速
-
更長的編譯時間以及花費在編譯器上的精力
RISC和CISC到底哪個更好,還沒有定論,事實上現在很多都是混合使用的。
13.6 RISC流水線
大部分指令是兩階段的,只包含了取指和執行,對于存取(Load and Store)則有三階段——取指(Instruction Fetch)、執行(Execute)(計算內存地址)和存儲(寄存器到內存或者內存到寄存器)。
圖(a)展示了沒有流水線的情況,圖(b)展示了兩階段的流水線,只包含了取指和執行這兩個階段,如果是load指令,則將E和D都看成是執行,可以看到這樣是存在流水線氣泡的,因為執行階段的時間太長,所以第二條的執行要延遲一拍。而圖(c)則是三階段的,就沒有了上面的問題,但是產生了新的問題,就是將rA的值和rB的值相加時,會存在依賴關系,因為需要等到第二條指令從內存中讀到操作數,也就是D結束,才能夠執行該命令,因此這里加了一條NOOP空過一個時拍。圖(d)也是同樣的道理,只不過需要加2條NOOP。
那么像上面這種還含有跳轉指令的該如何處理呢?
可以采用延遲轉移進行優化,我們來看一個例子。
延遲轉移:在轉移指令之后插入一條或幾條有效的指令。當程序執行時,要等這些插入的指令執行完成之后,才執行轉移指令,因此,轉移指令好像被延遲執行了,這種技術稱為延遲轉移技術,這一條或幾條指令被稱為延遲插槽。
| 100 | LOAD x,rA | LOAD X,rA | LOAD X,rA |
| 101 | ADD 1,rA | ADD 1,rA | JUMP 105 |
| 102 | JUMP 105 | JUMP 106 | ADD 1,rA |
| 103 | ADD rA,rB | NOOP | ADD rA,rB |
| 104 | SUB rC,rB | ADD rA,rB | SUB rC,rB |
| 105 | STORE A,Z | SUB rC,rB | STORE rA,Z |
| 106 | STORE rA,Z |
上表中,Normal為正常順序執行的流水線,而Delayed是用了一條NOOP空過一個時拍,上面已經講過了,而Optimized是使用了一些技巧進行優化的,我們來看一下流水線的情況:
可以看到,空過一個時拍可以解決問題,但還是效率低了些,如果我們把轉移指令上面的那一條指令插入到轉移指令之后作為一個延遲的插槽,于是這條插入的指令還是能夠正常執行的,且沒有依賴關系會導致流水線出錯,也就是說,這條指令充當了本來的NOOP指令,但是又真正執行了,于是效率就提高了。
今天的RISC就講到這里啦~
總結
以上是生活随笔為你收集整理的第十三章 RISC精简指令计算机的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: unity找不到报错界面
- 下一篇: 如何在信号中添加指定信噪比的高斯白噪声,