RISC和CISC,究竟有何不同?

關於RISC和CISC處理器的區別,大多數人會認為是一些特性、指令,或者是電晶體數量的差異。但實際上兩者之間的差別不能簡單的一概而論。

少量指令並不意味著RISC

首先,我們需要摒棄一些非常明顯的誤解。因為RISC的意思是簡化指令集計算機(Reduced Instruction Set Computer),所以很多人認為RISC處理器只是一個沒有多少指令的CPU。如果是這樣的話,那麼6502處理器將是有史以來最RISCy的處理器之一,它只有56條指令。甚至英特爾8086也可以算作RISC處理器,因為它只有81條指令。即使是後來的Intel 80286也只有大約100條指令。

像AVR這樣簡單的8位RISC處理器有78條指令。如果您看看最早的32位RISC處理器之一,比如PowerPC 601(1993年釋出),它有273個指令。

MIPS32指令集來源於伯克利的原始RISC處理器,它也有200多條指令。

我們可以將其與CISC 32位處理器(如80386)進行比較,後者只有略多於170條指令。差不多時間亮相的MIPS R2000處理器在大約有92條指令。

For the curios:

x86 instruction listings

Pentium instruction set

6502 Instruction Set

MIPS R2000 InstructionSet

古玩:

x86指令列表

奔騰指令集

6502指令集

MIPS R2000指令集

也就是說,類似x86指令集、奔騰指令集、6502指令集、MIPS R2000指令集一開始都具有很少指令集,但它們都不是RISC處理器。

少數電晶體並不意味著RISC

CISC和RISC處理器之間的電晶體數量的分界點是多少?根本沒有。6502有4528個電晶體。第一個ARM處理器有25000個電晶體。

或者這個有趣的小事實。摩托羅拉68060被認為是那個時代最具代表性的CISCy的處理器之一,它只有250萬個電晶體,比1994年釋出的IBM PowerPC 601的280萬個電晶體還要少。

如果你看一下幾乎同時釋出的RISC和CISC處理器,沒有明顯的趨勢表明RISC處理器比CISC處理器有更少的電晶體和更少的指令。

上世紀90年代初流行的RISC和CISC處理器電晶體和指令的比較

所以讓我們得出結論,我們不能根據電晶體或指令數量區分RISC或CISC晶片。但是問題仍然存在,到底是什麼是RISC微處理器或CISC微處理器?

RISC和CISC是不同的電晶體預算理念

當你的老闆告訴你“這裡,有一百萬個電晶體,給我做一個快速的處理器!”,那麼你就有很多方法可以實現這個目標。對於相同數量的電晶體,RISC和CISC的設計者將會做出不同的選擇。

伯克利的David A。 Patterson廣為人所知的可能是他在1980年發表的論文《簡化指令集計算機的案例》中推廣了RISC處理器的思想。

Patterson在這篇論文中概述的並不是晶片製造的詳細藍圖,而更像是哲學指導方針。

在現實世界的程式中,新增這個指令會提高多少效能?硬體方面的影響是什麼?我們是否需要儲存大量複雜的狀態,這使得上下文切換和無序執行更加複雜,因為需要儲存大量的狀態?

一個設計良好的簡單指令的組合能以相當的效能完成同樣的工作嗎?

我們是否可以利用現有的算術邏輯單元(ALUs)和CPU上的其他資源來新增這條指令,或者我們需要新增很多新東西?

如果不新增這條指令,這些電晶體的其他用途是什麼?更多的快取嗎?更好的分支預測嗎?

重要的是要理解這些規則適用於給定的電晶體預算。如果你有更多的電晶體,你可以新增更多的指令,甚至更復雜的指令。

然而,RISC的哲學優先考慮保持指令集的簡單。這意味著RISC設計者首先會嘗試透過其他方法來提高效能,而不是新增如下指令:

使用電晶體增加更多的快取更多的CPU暫存器更好的管道更好的分支預測超標量體系結構的體系結構新增更多的指令解碼器亂序執行Macro-operation融合壓縮指令

因此,設計一個好的RISC指令集(ISA)的一個關鍵目標是使設計不妨礙未來的微架構最佳化。

這與CISC設計者設計CPU的方式不同。為了能夠提供更好的效能,那麼CISC設計者將新增引入更多狀態以跟蹤狀態暫存器等複雜指令。

CISC設計理念的問題

問題是CISC的設計師沒有超前思考。將來你的電晶體預算可能會增加。突然之間,你有了所有這些好的電晶體,可以用來建立無序(OoO)超標標量處理器邏輯。這意味著您在每個時鐘週期解碼多個指令,並將它們放在一個指令佇列中。然後,OoO邏輯會找出哪些指令不相互依賴,以便它們可以並行執行。

如果您是軟體開發人員,您可以考慮函數語言程式設計(functional programming)和指令式程式設計(imperative programming)之間的區別。為了獲得短期效能收益而改變全域性資料可能很誘人。然而,一旦你並行執行,而全域性狀態被多個函式改變了,這可能會在多個執行緒中並行執行,這絕對是一場噩夢。

函數語言程式設計喜歡只依賴於輸入而不依賴全域性資料的純函式。這些函式可以很容易地並行執行。同樣的機制也適用於CPU。不依賴於全域性狀態(如狀態暫存器)的彙編程式碼指令可以更容易地並行或流水線執行。

RISC-V就是這種思想的一個很好的例子。RISC-V沒有狀態暫存器。比較和跳轉指令合二為一。除非執行額外的計算來確定是否發生了溢位,否則無法用狀態暫存器捕獲整數溢位。

這應該會給你一些關於RISC和CISC區別的線索。

一個RISC處理器設計的優先順序

如果10條新指令對微架構沒有顯著影響,那麼RISC設計者新增10條新指令不一定會有問題。如果一條指令要求在CPU中表示更多的全域性狀態,那麼RISC設計人員將會非常不願意新增一條指令。

這種哲學的最終結果是,從歷史上看,在RISC處理器上新增管道和超標量架構比CISC處理器更容易,因為人們避免了新增指令,從而引入狀態管理或控制邏輯,這使得新增這些微架構創新變得困難。

這就是為什麼RISC-V團隊更喜歡進行宏操作(macro-operation)融合,而不是新增支援複雜定址模式或整數溢位檢測的指令。

RISC的理念導致了不斷出現的特殊設計選擇,這讓我們能夠討論在比較RISC和CISC處理器時所觀察到的更具體的差異。讓我們看看這些。

現代RISC和CISC處理器的特點

某些設計選擇不斷出現在許多不同的RISC處理器上。通常情況下,RISC處理器傾向於使用固定長度的32位指令。也有一些例外,比如AVR,它使用固定長度的16位指令。相比之下,Intel x86處理器的指令長度為1到15位元組。摩托羅拉68k處理器,另一個著名的CISC設計,有2到10位元組長的指令(16位到80位)。

對於彙編程式設計師來說,變長指令實際上非常方便。我的第一臺電腦是Amiga 1000,它有一個摩托羅拉68k處理器,所以它向我介紹了68k組裝,坦白說非常整潔。它有將資料從一個記憶體位置移動到另一個記憶體位置的指令,或者可以將資料從暫存器A1給出的地址移動到另一個暫存器A2給出的記憶體位置。

; 68k Assembly codeMOVE。B 4, 12 ; mem[4] → mem[12]MOVE。B (A1), (A2) ; mem[A1] → mem[A2]

這樣的指令使CPU易於程式設計,但這意味著沒有辦法將支援的每條指令都放在32位內,因為表示完整的源地址和目的地址將只消耗64位。因此,透過使用變長指令,我們可以很容易地在任何指令中包含完整的32位記憶體地址。

然而,這種便利是有代價的。變長指令更難以流水線處理,如果你想讓一個超標標量處理器並行解碼兩條或多條指令,你很難做到這一點,因為你不知道每條指令在哪裡開始和結束,直到你解碼它們。

RISC和CISC,究竟有何不同?

使用超標量處理器,可以有多個指令解碼器並行工作。

RISC處理器傾向於避免使用可變長度指令,因為這不符合RISC不新增指令的理念,這也使得新增更高階的微架構最佳化變得更加困難。

固定長度的指令會造成不便。您不能將記憶體地址放入任何操作中,只能放入特定的操作,如載入和儲存指令。

RISC和CISC,究竟有何不同?

在RISC處理器中的算術邏輯單元(ALU)只能從暫存器而不是記憶體中獲取輸入。

載入/儲存體系結構

機器程式碼指令必須對正在執行的資訊進行編碼,例如它是在執行ADD、SUB還是MUL。它還必須對輸入的資訊進行編碼。輸入暫存器和輸出暫存器是什麼。一些指令需要對要載入資料的地址進行編碼。在RISC-V指令中是這樣編碼的:

上圖顯示瞭如何使用32位字中的每一位為RISC-V指令集編碼一條指令

我們執行的特定指令稱為操作碼(上圖黃色),它消耗7位。我們指定的每個暫存器輸入或輸出都需要5位。從這裡應該很清楚,擠入一個32位地址是不可能的。即使是一個較短的地址也是困難的,因為你需要位來指定在操作中使用的暫存器。對於CISC處理器,這不是一個問題,因為您可以自由地使用超過32位的指令。

這種緊湊的空間要求使得RISC處理器具有我們所說的載入/儲存架構。只有專用的載入和儲存指令,如RISC-V上的LW和SW,才能用於訪問記憶體。

對於CISC處理器,如68k,幾乎任何操作,如ADD、SUB、AND和OR,都可以使用記憶體地址作為運算元(引數)。在下面的例子中,4(A2)計算到一個記憶體地址,我們使用它來讀取一個運算元(引數)到ADD指令。最終的結果也儲存在那裡(在68k上destination是右引數)。

; 68k assemblyADD。L D3, 4(A2) ; D3 + mem[4 + A2] → mem[4 + A2]

典型的RISC處理器(如基於RISC- v指令集的處理器)需要將載入(LW)和儲存(SW)作為單獨的指令進行儲存。

# RISC-V assemblyLW x4, 4(x2) # x4 ← mem[x2+4]ADD x3, x4, x3 # x3 ← x4 + x3SW x3, 4(x2) # x3 → mem[x2+4]

你不需要透過結合地址暫存器(A0到A7)來計算地址。你可以直接指定一個記憶體地址,比如400:

; 68k assemblyADD。L 400, D4 ; mem[400] + D4 → D4

但即使是這樣一個看似簡單的操作也需要多個RISC指令。

# RISC-V assemblyLW x2, 400(x0) # x3 ← mem[x0 + 400]ADD x4, x4, x3 # x4 ← x4 + x3

在很多RISC設計中,x0暫存器總是0,這意味著即使你只對絕對記憶體地址感興趣,你也可以始終使用偏移加基暫存器的形式。雖然這些偏移量看起來與您在68k上所做的非常相似,但它們的限制要大得多,因為您總是需要適合一個32位字。使用68k,可以給ADD。L一個完整的32位地址。你不能用RISC-V LW和SW。獲得完整的32位地址是相當麻煩的。假設您希望從32位地址:0x00042012載入資料,則必須分別載入上面的20位和下面的12位,以形成一個32位地址。

# RISC-V assemblyLUI x3, 0x42 # x3[31:12] ← 0x42 put in upper 20-bitsADDI x3, x3, 0x12 # x3 ← x3 + x3 + 0x12LW x4, 0(x3) # x4 ← mem[x3+0]

實際上這可以簡化為:LUI x3, 0x42 LW x4, 0x12(x3)

我記得當我從68k組裝轉到PowerPC(蘋果以前使用的RISC處理器)時,這讓我很惱火。當時我認為RISC意味著一切都將變得更容易。我發現x86很麻煩,很難處理。然而,對於彙編編碼員來說,RISC不像68k那樣方便地使用CISC指令集。幸運的是,有一些簡單的技巧可以使這個過程在RISC處理器上變得更容易。RISC-V定義了一些偽指令,以簡化彙編程式碼的編寫。使用LA (load address)偽指令,我們可以像這樣編寫前面的程式碼:

# RISC-V assembly with pseudo instructionsLI x3, 0x00042012 # Expands to a LUI and ADDILW x4, 0(x3)

總而言之:雖然載入/儲存體系結構使編寫彙編程式碼變得更麻煩,但它允許我們保持每個指令為32位長。這意味著建立一個可以並行解碼多個指令的超標標量微體系結構需要更少的電晶體來實現。流水線化每條指令變得更容易,因為它們中的大多數可以適合經典的5步RISC流水線。

RISC處理器有很多暫存器

使用像68k這樣的高階CISC處理器,您可以用一條指令做很多事情。假設您想將數字從一個數組複製到另一個數組。下面是一個使用指標的C語言例子:

// C codeint data[4] = {4, 8, 1, 2, -1};int *src = data;while (*xs > 0)*dst++ = *src++;

如果你在68k處理器上將指標src儲存在地址暫存器A0中,將指標dst儲存在地址暫存器A1中,你可以在一條指令中複製並向前移動每個指標4個位元組:

; 68k assemblyMOVE。L (A0)+,(A1)+ ; mem[A1++] → mem[A2++]

這只是一個例子,但是一般來說,您可以使用CISC指令做更多的事情。這意味著您需要更少的程式碼。因此,RISC設計者意識到他們的程式碼會變得臃腫。因此,RISC的設計者們分析了真實的程式碼,提出了一種方案,可以在不使用複雜指令的情況下減少程式碼的大小。他們發現很多程式碼只是用來載入和儲存記憶體中的資料。透過新增大量暫存器,可以將臨時結果儲存在暫存器中,而無需將它們寫入記憶體。這將減少需要執行的載入和儲存指令的數量,從而減少程式碼的RISC程式碼大小。

因此,MIPS、SPARC、Arm(64位)和RISC-V處理器有32個通用暫存器。我們可以對比一下原來的x86,它只有8個通用暫存器。

RISC和CISC,究竟有何不同?

複雜性的RISC/CISC視角

我在這個故事中想要說明的是,RISC處理器並不比CISC處理器差。區別在於RISC和CISC的設計者選擇增加複雜性。

CISC設計人員將複雜性放在指令集體系結構(ISA)中,而RISC設計人員寧願將複雜性新增到他們的微體系結構中,但正如我一直強調的,他們不希望指令集在微架構中強加複雜性。

讓我來比較一下MIPS R4000,摩托羅拉68040和英特爾486,以強調理念上的差異。它們都有大約120萬個電晶體,幾乎同時釋出(1989年至1991年)。

RISC處理器(R4000)是64位的,其他是32位的。

R4000有8級pipeline ,允許比6級pipeline 的68040和5級pipeline 的486更高的時鐘頻率。

更長的pipeline 給R4000從100-200 Mhz遠遠超過68040的40Mhz和486DX2得到66Mhz (100Mhz在一個更晚的模型)。

最終在1993/1994年出現了速度更快的CISC處理器,如68060和Pentium。但與此同時出現了MIPS R8000,它是一種可以並行解碼4條指令的超標量結構。奔騰處理器每個時鐘週期只能解碼2條指令。

所以我們可以看到RISC的設計者們是如何喜歡花哨的微架構而不是花哨的指令的。

“但是現代CISC處理器有複雜的微架構!”

您可能會抗議說,今天的CISC處理器有複雜的微架構。他們所做的。一個現代的Intel或AMD處理器有多個解碼器、微操作快取、高階分支預測器、無序(OoO)執行引擎。然而,這並不奇怪。記住我關於電晶體預算理念的關鍵點:今天每個人都有很多電晶體可以使用,所以所有高階晶片都將有很多先進的微架構功能。他們可以負擔得起他們的預算。

關鍵在於:這些複雜的微體系結構特性中的許多都是由複雜的CISC指令集強加的。例如,為了使pipelines 工作,x86處理器將其冗長複雜的指令分解成微操作。微操作很簡單,行為更像RISC操作,因此它們可以更容易地流水線化。

問題是把CISC指令分解成更簡單的微觀操作並不容易。因此,許多現代的超標標量x86處理器對簡單指令有3個指令解碼器,對複雜指令有1個解碼器。由於您不知道每條指令從哪裡開始和結束,CISC處理器不得不進行一場涉及許多電晶體的複雜的猜謎遊戲。

RISC處理器避免了這種複雜性,可以將所有浪費的電晶體用於新增更多的解碼器或進行其他最佳化,如使用壓縮指令或宏操作融合(將非常簡單的指令組合成更復雜的指令)。

在不同的CPU設計中,指令可以以不同的方式組合或分割。

如果你將蘋果的M1處理器(這是一個基於RISC的處理器)與AMD和英特爾的處理器做比較,你會注意到它有更多的指令解碼器。CISC的設計人員試圖透過新增微操作快取來緩解這個問題。有了微操作快取,CISC處理器就不必克服障礙,也不必一遍又一遍地解碼相同的複雜指令。然而,增加這一功能顯然會消耗電晶體的成本。它不是免費的。因此,你正在把你的電晶體預算浪費在微架構的複雜性上,這只是因為ISA的複雜性。

Arm vs RISC-V設計理念

比較現代RISC和CISC處理器的一個問題是,RISC基本上贏了。沒有人再從頭開始設計CISC處理器了。Intel和AMD的x86處理器今天之所以流行,主要是因為向後相容。

如果你今天讓一個設計團隊坐下來,告訴他們從頭開始設計一個高效能處理器,那麼你最終不會得到傳統的CISC設計。

然而,這並不意味著在RISC社群中,有多少設計師傾向於CISC或RISC的方向上沒有差異。現代的Arm處理器和基於RISC-V的處理器就是這種對比的有趣例子。

Arm的設計者更願意新增複雜的指令來提高效能。請記住,不是Arm不是RISC設計。當你的電晶體預算增長,增加更復雜的指令是公平的。。

RISC-V的設計者更熱衷於將ISA的複雜性保持在最低程度,而不是增加微架構的複雜性,從而透過使用壓縮指令和宏操作融合等技巧來提高效能。我在這裡討論這些設計選擇:RISC-V微處理器的天才。

Arm和RISC-V的不同選擇不是隨意的,而是受到非常不同的目標和市場的很大影響。Arm越來越多地進入高階市場。請記住,蘋果的Arm晶片正在與x86晶片展開正面競爭,不久,英偉達也會這樣做。

RISC-V的目標是成為一個更廣泛的架構,用於從鍵盤到人工智慧加速器、從gGPU到專門的超級計算機的任何東西。這意味著RISC-V意味著靈活性,您新增的指令越複雜,您施加的複雜性就越小,從而減少了為特定用例定製晶片的自由。