初識(shí)計(jì)算機(jī)組成原理-處理器篇

處理器

指令周期

計(jì)算機(jī)每執(zhí)行一條指令的過(guò)程寸癌,可以分解成這樣幾個(gè)步驟升薯,然后一直重復(fù)

  1. Fetch(取得指令):也就是從 PC 寄存器里找到對(duì)應(yīng)的指令地址尤勋,根據(jù)指令地址從內(nèi)存里把具體的指令,加載到指令寄存器中懈叹,然后把 PC 寄存器自增,好在未來(lái)執(zhí)行下一條指令萝毛。
  2. Decode(指令譯碼):也就是根據(jù)指令寄存器里面的指令项阴,解析成要進(jìn)行什么樣的操作,是 R笆包、I、J 中的哪一種指令略荡,具體要操作哪些寄存器庵佣、數(shù)據(jù)或者內(nèi)存地址。
  3. Execute(執(zhí)行指令):也就是實(shí)際運(yùn)行對(duì)應(yīng)的 R汛兜、I巴粪、J 這些特定的指令,進(jìn)行算術(shù)邏輯操作粥谬、數(shù)據(jù)傳輸或者直接的地址跳轉(zhuǎn)肛根。

這樣一個(gè)周期就是指令周期。從拿到漏策,到執(zhí)行結(jié)束派哲。其中指令周期中不同部分由計(jì)算機(jī)不同部分執(zhí)行。

  • 存儲(chǔ)器:取指令的階段掺喻,指令是放在存儲(chǔ)器里的芭届。
  • 控制器:通過(guò) PC 寄存器和指令寄存器取出指令的過(guò),是由控制器操作的感耙。指令的解碼過(guò)程褂乍,也是由控制器進(jìn)行的。
  • 運(yùn)算器:一旦到了執(zhí)行指令階段即硼,無(wú)論是進(jìn)行算術(shù)操作逃片、邏輯操作的 R 型指令,還是進(jìn)行數(shù)據(jù)傳輸只酥、條件分支的 I 型型指令褥实,都是由算術(shù)邏輯單元(ALU)操作的。如果是一個(gè)簡(jiǎn)單的無(wú)條件地址跳轉(zhuǎn)层皱,那么我們可以直接在控制器里面完成性锭,不需要用到運(yùn)算器。
image

CPU 周期:一般把從內(nèi)存里面讀取一條指令的最短時(shí)間稱為 CPU 周期叫胖。

一個(gè) CPU 周期草冈,通常會(huì)由幾個(gè)時(shí)鐘周期累積起來(lái)。一個(gè) CPU 周期的時(shí)間,就是這幾個(gè) Clock Cycle 的總和怎棱。 對(duì)于一個(gè)指令周期來(lái)說(shuō)哩俭,取出一條指令,然后執(zhí)行它拳恋,至少需要兩個(gè) CPU 周期凡资。取出指令至少需要一個(gè) CPU 周期,執(zhí)行至少也需要一個(gè) CPU 周期谬运,復(fù)雜的指令則需要更多的 CPU 周期隙赁。

指令周期,CPU 周期梆暖,時(shí)鐘周期的關(guān)系:


image

處理器由兩類原件組成:

  1. 操作原件:ALU
  2. 存儲(chǔ)原件:寄存器

所有 CPU 支持的指令伞访,都會(huì)在控制器里面,被解析成不同的輸出信號(hào)『洳担現(xiàn)在的 Intel CPU 支持 2000 個(gè)以上的指令厚掷。則控制器輸出的控制信號(hào)對(duì)應(yīng)的至少也有 2000 種不同的組合。

運(yùn)算器里的 ALU 和各種組合邏輯電路级解,可以認(rèn)為是一個(gè)固定功能的電路冒黑。控制器“翻譯”出來(lái)的勤哗,就是不同的控制信號(hào)抡爹。這些控制信號(hào),告訴 ALU 去做不同的計(jì)算俺陋』硌樱可以說(shuō)正是控制器的存在,讓我們可以“編程”來(lái)實(shí)現(xiàn)功能腊状。

image

指令譯碼器將輸入的機(jī)器碼诱咏,解析成不同的操作碼和操作數(shù),然后傳輸給 ALU 進(jìn)行計(jì)算缴挖。

CPU 所需要的硬件電路

  1. ALU:加法器袋狞,乘法器等。
  2. 寄存器:鎖存器映屋,D 觸發(fā)器
  3. 能引起 PC 寄存器一直自增的計(jì)數(shù)器電路苟鸯。
  4. 編碼及尋址的譯碼器電路:無(wú)論是對(duì)于指令進(jìn)行 decode,還是對(duì)于拿到的內(nèi)存地址去獲取對(duì)應(yīng)的數(shù)據(jù)或者指令棚点,我們都需要通過(guò)一個(gè)電路找到對(duì)應(yīng)的數(shù)據(jù)早处。

這樣 CPU 好像一個(gè)永不停歇的機(jī)器,一直在不停地讀取下一條指令運(yùn)行瘫析。那為什么CPU 還會(huì)有滿載運(yùn)行和 Idle 閑置的狀態(tài)呢砌梆?

  1. CPU在空閑狀態(tài)就會(huì)停止執(zhí)行默责,具體來(lái)說(shuō)就是切斷時(shí)鐘信號(hào),CPU的主頻就會(huì)瞬間降低為0咸包,功耗也會(huì)瞬間降低為0桃序。由于這個(gè)空閑狀態(tài)是十分短暫的,所以你在任務(wù)管理器里面也只會(huì)看到CPU頻率下降烂瘫,不會(huì)看到降低為0媒熊。當(dāng)CPU從空閑狀態(tài)中恢復(fù)時(shí),就會(huì)接通時(shí)鐘信號(hào)坟比,這樣CPU頻率就會(huì)上升芦鳍。所以你會(huì)在任務(wù)管理器里面看到CPU的頻率起伏變化。
  2. 操作系統(tǒng)內(nèi)核有 idle 進(jìn)程葛账,優(yōu)先級(jí)最低怜校,僅當(dāng)其他進(jìn)程都阻塞時(shí)被調(diào)度器選中。idle 進(jìn)程循環(huán)執(zhí)行 HLT 指令注竿,關(guān)閉 CPU 大部分功能以降低功耗,收到中斷信號(hào)時(shí) CPU 恢復(fù)正常狀態(tài)魂贬。

時(shí)序邏輯電路

時(shí)序邏輯電路可以解決的問(wèn)題:

  • 自動(dòng)運(yùn)行:時(shí)序電路接通之后可以不停地開啟和關(guān)閉開關(guān)巩割,進(jìn)入一個(gè)自動(dòng)運(yùn)行的狀態(tài)须眷。-> PC 寄存器
  • 存儲(chǔ):通過(guò)時(shí)序電路實(shí)現(xiàn)的觸發(fā)器为黎,能把計(jì)算結(jié)果存儲(chǔ)在特定的電路里面锈至,而不是像組合邏輯電路那樣佑力,固定的輸入輸出级历。一旦輸入有任何改變腥光,對(duì)應(yīng)的輸出也會(huì)改變懒构。

CPU 的主頻是由一個(gè)類似晶體振蕩器的電路來(lái)實(shí)現(xiàn)的众羡,而這個(gè)晶體振蕩器生成的電路信號(hào)勋颖,就是時(shí)鐘信號(hào)嗦嗡。

實(shí)現(xiàn)這樣一個(gè)電路只需如圖這樣一個(gè)電路即可:

image

開關(guān) A 閉合(也就是相當(dāng)于接通電路之后),開關(guān) B 就會(huì)不就會(huì)不停地在開和關(guān)之間切換饭玲,生成對(duì)應(yīng)的時(shí)鐘信號(hào)侥祭。這個(gè)不斷切換的過(guò)程,對(duì)于下游電路來(lái)說(shuō)茄厘,就是不斷地產(chǎn)生新的 0 和 1 這樣的信號(hào)矮冬。這個(gè)按照固定的周期不斷在 0 和 1 之間切換的信號(hào),就是時(shí)鐘信號(hào)次哈。

有了時(shí)鐘信號(hào)胎署,利用這樣一個(gè)電路,就可以構(gòu)造出有記憶功能的電路窑滞,如寄存器和存儲(chǔ)器琼牧。如圖RS-觸發(fā)器電路:

image

接通開關(guān) R恢筝,輸出變?yōu)?1,即使斷開開關(guān)障陶,輸出還是 1 不變滋恬。接通開關(guān) S,輸出變?yōu)?0抱究,即使斷開開關(guān)恢氯,輸出也還是 0。也就是鼓寺,當(dāng)兩個(gè)開關(guān)都斷開的時(shí)候勋拟,最終的輸出結(jié)果,取決于之前動(dòng)作的輸出結(jié)果妈候,這個(gè)也就是我們說(shuō)的記憶功能敢靡。 其中當(dāng)兩個(gè)開關(guān)都為 0 的時(shí)候,對(duì)應(yīng)的輸出不是 1 或者 0苦银,而是和 Q 的上一個(gè)狀態(tài)一致啸胧。

通過(guò)引入了時(shí)序電路,數(shù)據(jù)就可以“存儲(chǔ)”下來(lái)了:通過(guò)反饋電路幔虏,創(chuàng)建了時(shí)鐘信號(hào)纺念,然后再利用這個(gè)時(shí)鐘信號(hào)和門電路組合,實(shí)現(xiàn)了“狀態(tài)記憶”的功能想括。 電路的輸出信號(hào)不單單取決于當(dāng)前的輸入信號(hào)陷谱,還要取決于輸出信號(hào)之前的狀態(tài)。最常見(jiàn)的這個(gè)電路就是 D 觸發(fā)器瑟蜈,它也是實(shí)際在 CPU 內(nèi)實(shí)現(xiàn)存儲(chǔ)功能的寄存器的實(shí)現(xiàn)方式烟逊。

單指令周期處理器

有了時(shí)鐘信號(hào)和D觸發(fā)器,就有了一個(gè)每過(guò)一個(gè)時(shí)鐘周期铺根,就會(huì)固定自增 1 的程序計(jì)數(shù)器即PC寄存器宪躯。

加法器的兩個(gè)輸入,一個(gè)始終設(shè)置成 1夷都,另外一個(gè)來(lái)自于一個(gè) D 型觸發(fā)器 A眷唉。我們把加法器的輸出結(jié)果,寫到這個(gè) D 型觸發(fā)器 A 里面囤官。于是冬阳,D 型觸發(fā)器里面的數(shù)據(jù)就會(huì)在固定的時(shí)鐘信號(hào)為 1 的時(shí)候更新一次。如圖:

image

加法計(jì)數(shù)党饮、內(nèi)存取值肝陪,乃至后面的命令執(zhí)行,最終其實(shí)都是由時(shí)鐘信號(hào)刑顺,來(lái)控制執(zhí)行時(shí)間點(diǎn)和先后順序的氯窍。

在最簡(jiǎn)單的情況下饲常,我們需要讓每一條指令,從程序計(jì)數(shù)狼讨,到獲取指令贝淤、執(zhí)行指令,都在一個(gè)時(shí)鐘周期內(nèi)完成政供。 因?yàn)槿绻?PC 寄存器自增地太快播聪,程序就會(huì)出錯(cuò)。因?yàn)榍耙淮蔚倪\(yùn)算結(jié)果還沒(méi)有寫回到對(duì)應(yīng)的寄存器里面的時(shí)候布隔,后面一條指令已經(jīng)開始讀取寄存器里面的數(shù)據(jù)來(lái)做下一次計(jì)算了离陶。這個(gè)時(shí)候,如果我們的指令使用同樣的寄存器衅檀,前一條指令的計(jì)算就會(huì)沒(méi)有效果招刨,計(jì)算結(jié)果就錯(cuò)了。

在這種設(shè)計(jì)下哀军,就需要在一個(gè)時(shí)鐘周期里沉眶,確保執(zhí)行完一條最復(fù)雜的 CPU 指令,也就是耗時(shí)最長(zhǎng)的一條 CPU指令杉适。這樣的 CPU 設(shè)計(jì)就是單指令周期處理器沦寂。 這樣的設(shè)計(jì)有點(diǎn)兒浪費(fèi)。因?yàn)榧幢阒徽{(diào)用一條非常簡(jiǎn)單的指令淘衙,整個(gè)處理器也需要等待整個(gè)時(shí)鐘周期的時(shí)間走完,才能執(zhí)行下一條指令腻暮。-> 通過(guò)流水線技術(shù)進(jìn)行性能優(yōu)化彤守,可以減少需要等待的時(shí)間

其實(shí)譯碼器的本質(zhì),就是從輸入的多個(gè)位的信號(hào)中哭靖,根據(jù)一定的開關(guān)和電路組合具垫,選擇出自己想要的信號(hào)。除了能夠進(jìn)行“尋址”之外试幽,還可以把對(duì)應(yīng)的需要運(yùn)行的指令碼筝蚕,同樣通過(guò)譯碼器,找出我們期望執(zhí)行的指令铺坞,也就是在之前的 opcode起宽,以及后面對(duì)應(yīng)的操作數(shù)或者寄存器地址。

image
  1. 首先济榨,有一個(gè)自動(dòng)計(jì)數(shù)器坯沪。這個(gè)自動(dòng)計(jì)數(shù)器會(huì)隨著時(shí)鐘主頻不斷地自增,來(lái)作為 PC 寄存器擒滑。
  2. 在這個(gè)自動(dòng)計(jì)數(shù)器的后面腐晾,連上一個(gè)譯碼器叉弦。譯碼器還要同時(shí)連著我們通過(guò)大量的 D 觸發(fā)器組成的內(nèi)存。
  3. 自動(dòng)計(jì)數(shù)器會(huì)隨著時(shí)鐘主頻不斷自增藻糖,從譯碼器當(dāng)中淹冰,找到對(duì)應(yīng)的計(jì)數(shù)器所表示的內(nèi)存地址,然后讀取出里面的 CPU 指令巨柒。
  4. 讀取出來(lái)的 CPU 指令會(huì)通過(guò)我們的 CPU 時(shí)鐘的控制樱拴,寫入到一個(gè)由 D 觸發(fā)器組成的寄存器,也就是指令寄存器當(dāng)中潘拱。
  5. 在指令寄存器后面疹鳄,我們可以再跟一個(gè)譯碼器。這個(gè)譯碼器不再是用來(lái)尋址的了芦岂,而是把我們拿到的指令瘪弓,解析成 opcode 和對(duì)應(yīng)的操作數(shù)。
  6. 當(dāng)我們拿到對(duì)應(yīng)的 opcode 和操作數(shù)禽最,對(duì)應(yīng)的輸出線路就要連接 ALU腺怯,開始進(jìn)行各種算術(shù)和邏輯運(yùn)算。對(duì)應(yīng)的計(jì)算結(jié)果川无,則會(huì)再寫回到 D 觸發(fā)器組成的寄存器或者內(nèi)存當(dāng)中呛占。

從上述指令的執(zhí)行流程可以看出,我們可以在一個(gè)時(shí)鐘周期里面懦趋,去自增 PC 寄存器的值晾虑,也就是指令對(duì)應(yīng)的內(nèi)存地址。然后仅叫,我們要根據(jù)這個(gè)地址從 D 觸發(fā)器里面讀取指令帜篇,這個(gè)還是可以在剛才那個(gè)時(shí)鐘周期內(nèi)。但是對(duì)應(yīng)的指令寫入到指令寄存器诫咱,我們可以放在一個(gè)新的時(shí)鐘周期里面笙隙。指令譯碼給到 ALU 之后的計(jì)算結(jié)果,要寫回到寄存器坎缭,又可以放到另一個(gè)新的時(shí)鐘周期竟痰。所以,執(zhí)行一條計(jì)算機(jī)指令掏呼,其實(shí)可以拆分到很多個(gè)時(shí)鐘周期坏快,而不是必須使用單指令周期處理器的設(shè)計(jì)。

因?yàn)閺膬?nèi)存里面讀取指令時(shí)間很長(zhǎng)憎夷,所以如果使用單指令周期處理器假消,就意味著我們的指令都要去等待一些慢速的操作。這些不同指令執(zhí)行速度的差異岭接,也正是計(jì)算機(jī)指令有指令周期富拗、CPU 周期和時(shí)鐘周期之分的原因臼予。因此,現(xiàn)代我們優(yōu)化 CPU 的性能時(shí)啃沪,用的 CPU 都不是單指令周期處理器粘拾,而是通過(guò)流水線、分支預(yù)測(cè)等技術(shù)创千,來(lái)實(shí)現(xiàn)在一個(gè)周期里同時(shí)執(zhí)行多個(gè)指令缰雇。

面向流水線的指令設(shè)計(jì)

不同指令的執(zhí)行時(shí)間不同,隨著門電路層數(shù)的增加追驴,由于門延遲的存在械哟,位數(shù)多、計(jì)算復(fù)雜的指令需要的執(zhí)行時(shí)間會(huì)更長(zhǎng)殿雪。但是我們需要讓所有指令都在一個(gè)時(shí)鐘周期內(nèi)完成暇咆,如單指令周期處理器那樣,那就只好把時(shí)鐘周期和執(zhí)行時(shí)間最長(zhǎng)的那個(gè)指令設(shè)成一樣丙曙。也就意味著快速執(zhí)行完成的指令爸业,需要等待滿一個(gè)時(shí)鐘周期,才能執(zhí)行下一條指令亏镰,如圖:

image

由此因?yàn)閺?fù)雜指令執(zhí)行時(shí)間的制約扯旷,處理器的時(shí)鐘頻率就沒(méi)法太高。因?yàn)樘叩脑捤髯ィ行?fù)雜指令沒(méi)有辦法在一個(gè)時(shí)鐘周期內(nèi)運(yùn)行完成钧忽。那么在下一個(gè)時(shí)鐘周期到來(lái),開始執(zhí)行下一條指令的時(shí)候逼肯,前一條指令的執(zhí)行結(jié)果可能還沒(méi)有寫入到寄存器里面惰瓜。那下一條指令讀取的數(shù)據(jù)就是不準(zhǔn)確的,就會(huì)出現(xiàn)錯(cuò)誤汉矿。 如圖,超時(shí)寫入即無(wú)效备禀,這次改動(dòng)相當(dāng)于丟失了洲拇。

image

因?yàn)?CPU 的指令執(zhí)行過(guò)程,其實(shí)也是由各個(gè)電路模塊組成的曲尸。這些都是一個(gè)一個(gè)獨(dú)立的組合邏輯電路赋续,完全可以把他們進(jìn)行拆分。如下:

image

這樣一來(lái)另患,就不用把時(shí)鐘周期設(shè)置成整條指令執(zhí)行的時(shí)間纽乱,而是拆分成完成這樣的一個(gè)一個(gè)小步驟需要的時(shí)間。同時(shí)昆箕,每一個(gè)階段的電路在完成對(duì)應(yīng)的任務(wù)之后鸦列,也不需要等待整個(gè)指令執(zhí)行完成租冠,而是可以直接執(zhí)行下一條指令的對(duì)應(yīng)階段。

如果我們把一個(gè)指令拆分成“取指令 - 指令譯碼 - 執(zhí)行指令”這樣三個(gè)部分薯嗤,那這就是一個(gè)三級(jí)的流水線顽爹。如果我們進(jìn)一步把“執(zhí)行指令”拆分成“ALU 計(jì)算(指令執(zhí)行)- 內(nèi)存訪問(wèn) - 數(shù)據(jù)寫回”,那么它就會(huì)變成一個(gè)五級(jí)的流水線骆姐。

五級(jí)的流水線镜粤,就表示我們?cè)谕粋€(gè)時(shí)鐘周期里面,同時(shí)運(yùn)行五條指令的不同階段玻褪。這個(gè)時(shí)候肉渴,雖然執(zhí)行一條指令的時(shí)鐘周期變成了 5,但是我們可以把 CPU 的主頻提得更高了带射。我們不需要確保最復(fù)雜的那條指令在時(shí)鐘周期里面執(zhí)行完成同规,而只要保障一個(gè)最復(fù)雜的==流水線級(jí)==的操作,在一個(gè)時(shí)鐘周期內(nèi)完成就好了庸诱。

如果某一個(gè)操作步驟的時(shí)間太長(zhǎng)捻浦,我們就可以考慮把這個(gè)步驟,拆分成更多的步驟桥爽,讓所有步驟需要執(zhí)行的時(shí)間盡量都差不多長(zhǎng)朱灿。 這樣,在通過(guò)同時(shí)在執(zhí)行多條指令的不同階段钠四,我們提升了 CPU 的“吞吐率”盗扒。在外部看來(lái),CPU 好像是“一心多用”缀去,在同一時(shí)間侣灶,同時(shí)執(zhí)行 5 條不同指令的不同階段。在 CPU 內(nèi)部缕碎,其實(shí)它就像生產(chǎn)線一樣褥影,不同分工的組件不斷處理上游傳遞下來(lái)的內(nèi)容,而不需要等待單件商品生產(chǎn)完成之后咏雌,再啟動(dòng)下一件商品的生產(chǎn)過(guò)程凡怎。

需要注意的是:

增加流水線深度,在同主頻下赊抖,其實(shí)是降低了 CPU 的性能统倒。因?yàn)橐粋€(gè) Pipeline Stage,就需要一個(gè)時(shí)鐘周期氛雪。那么我們把任務(wù)拆分成 31 個(gè)階段房匆,就需要 31 個(gè)時(shí)鐘周期才能完成一個(gè)任務(wù);而把任務(wù)拆分成 11 個(gè)階段,就只需要 11 個(gè)時(shí)鐘周期就能完成任務(wù)浴鸿。在這種情況下井氢,31 個(gè) Stage 的 3GHz 主頻的 CPU,其實(shí)和 11 個(gè) Stage 的 1GHz 主頻的 CPU赚楚,性能是差不多的毙沾。事實(shí)上,因?yàn)槊總€(gè) Stage 都需要有對(duì)應(yīng)的 Pipeline 寄存器的開銷宠页,這個(gè)時(shí)候左胞,更深的流水線性能可能還會(huì)更差一些。

image

多級(jí)流水線技術(shù)的挑戰(zhàn):

  1. 功耗問(wèn)題:由于提升了流水線深度举户,所以為了保持和原來(lái)相同的性能烤宙,CPU主頻也要同步提升到對(duì)應(yīng)的頻率。同時(shí)俭嘁,由于流水線深度的增加躺枕,我們需要的電路數(shù)量變多了,也就是我們所使用的晶體管也就變多了供填。主頻的提升和晶體管數(shù)量的增加都使得我們 CPU 的功耗變大了拐云。
  2. 指令依賴問(wèn)題:流水線技術(shù)帶來(lái)的性能提升,是一個(gè)理想情況近她。在實(shí)際的程序執(zhí)行中叉瘩,并不一定能夠做得到。 因?yàn)闀?huì)有計(jì)算機(jī)組成里所說(shuō)的“冒險(xiǎn)”問(wèn)題粘捎。比如數(shù)據(jù)冒險(xiǎn)薇缅,結(jié)構(gòu)冒險(xiǎn)、控制冒險(xiǎn)等其他的依賴問(wèn)題攒磨。如下代碼段泳桦,三條指令間相互依賴,流水線越長(zhǎng),這個(gè)冒險(xiǎn)的問(wèn)題就越難一解決。因?yàn)榉B疲粫r(shí)間同時(shí)在運(yùn)行的指令太多了。如果只有 3 級(jí)流水線浮毯,我們可以把后面沒(méi)有依賴關(guān)系的指令放到前面來(lái)執(zhí)行。但如果我們有 20 級(jí)流水線演痒,意味著我們要確保這 20 條指令之間沒(méi)有依賴關(guān)系。這個(gè)挑戰(zhàn)一下子就變大了很多趋惨。畢竟我們平時(shí)撰寫程序鸟顺,通常前后的代碼都是有一定的依賴關(guān)系的,幾十條沒(méi)有依賴關(guān)系的,幾十條沒(méi)有依賴關(guān)系的指令可不好找讯嫂。
數(shù)據(jù)冒險(xiǎn):
int a = 10 + 5; // 指令 1
int b = a * 2; // 指令 2
float c = b * 1.0f; // 指令 3

冒險(xiǎn)和預(yù)測(cè)

CPU 流水線設(shè)計(jì)需要解決的三大冒險(xiǎn):結(jié)構(gòu)冒險(xiǎn)蹦锋,數(shù)據(jù)冒險(xiǎn),控制冒險(xiǎn)欧芽。

結(jié)構(gòu)冒險(xiǎn)

結(jié)構(gòu)冒險(xiǎn)莉掂,本質(zhì)上是一個(gè)硬件層面的資源競(jìng)爭(zhēng)問(wèn)題,也就是一個(gè)硬件電路層面的問(wèn)題千扔。CPU 在同一個(gè)時(shí)鐘周期憎妙,同時(shí)在運(yùn)行兩條計(jì)算機(jī)指令的不同階段,可能會(huì)用到同樣的硬件電路曲楚。

如圖:在第 1 條指令執(zhí)行到訪存(MEM)階段的時(shí)候厘唾,流水線里的第 4 條指令,在執(zhí)行取指令(Fetch)的操作龙誊。訪存和取指令抚垃,都要進(jìn)行內(nèi)存數(shù)據(jù)的讀取。我們的內(nèi)存趟大,只有一個(gè)地址譯碼器的作為地址輸入鹤树,那就只能在一個(gè)時(shí)鐘周期里面讀取一條數(shù)據(jù),沒(méi)辦法同時(shí)執(zhí)行第 1 條指令的讀取內(nèi)存數(shù)據(jù)和第 4 條指令的讀取指令代碼逊朽。

image

解決辦法:本質(zhì)上就是增加資源罕伯,對(duì)于訪問(wèn)內(nèi)存數(shù)據(jù)和取指令的沖突,一個(gè)直觀的解決方案就是把我們的內(nèi)存分成兩部分惋耙,讓它們各有各的地址譯碼器捣炬。這兩部分分別是存放指令的程序內(nèi)存存放數(shù)據(jù)的數(shù)據(jù)內(nèi)存。 這樣把內(nèi)存拆成兩部分的解決方案绽榛,在計(jì)算機(jī)體系結(jié)構(gòu)里叫作哈佛架構(gòu)湿酸。

但如果如上面這樣拆的話對(duì)程序指令和數(shù)據(jù)需要的內(nèi)存空間,我們就沒(méi)有辦法根據(jù)實(shí)際的應(yīng)用去動(dòng)態(tài)分配了灭美。雖然解決了資源沖突的問(wèn)題推溃,但是也失去了靈活性。現(xiàn)代 CPU 架構(gòu)届腐,借鑒了哈佛架構(gòu)铁坎,在高速緩存層面拆分成指令緩存和數(shù)據(jù)緩存。

image

內(nèi)存的訪問(wèn)速度遠(yuǎn)比 CPU 的速度要慢犁苏,所以現(xiàn)代的 CPU 并不會(huì)直接讀取主內(nèi)存硬萍。它會(huì)從主內(nèi)存把指令和數(shù)據(jù)加載到高速緩存中,這樣后續(xù)的訪問(wèn)都是訪問(wèn)高速緩存围详。 而指令緩存和數(shù)據(jù)緩存的拆分朴乖,使得我們的 CPU 在進(jìn)行數(shù)據(jù)訪問(wèn)和取指令的時(shí)候祖屏,不會(huì)再發(fā)生資源沖突的問(wèn)題了。

數(shù)據(jù)冒險(xiǎn)

數(shù)據(jù)冒險(xiǎn):它是程序邏輯層面的事情买羞,其實(shí)就是同時(shí)在執(zhí)行的多個(gè)指令之間袁勺,有數(shù)據(jù)依賴的情況。這些數(shù)據(jù)依賴畜普,可以分成三大類期丰,分別是先寫后讀先讀后寫吃挑,和寫后再寫钝荡。

先寫后讀案例:

int main() {
  int a = 1;
  int b = 2;
  a = a + 2;
  b = a + 3;
}
===============================================================
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
  int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  int b = 2;
   b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2 
  a = a + 2;
  12:   83 45 fc 02             add    DWORD PTR [rbp-0x4],0x2          把 0x2 添加到 rbp-0x4 對(duì)應(yīng)的內(nèi)存地址里面。
  b = a + 3;
  16:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]          從 rbp-0x4 這個(gè)內(nèi)存地址里面儒鹿,把數(shù)據(jù)寫入到 eax 這個(gè)寄存器里面化撕。
  19:   83 c0 03                add    eax,0x3                          把 0x3 添加到 eax 對(duì)應(yīng)的寄存器上。
  1c:   89 45 f8                mov    DWORD PTR [rbp-0x8],eax          從 eax 寄存器中约炎,把數(shù)據(jù)寫入到 [rbp-0x8] 這個(gè)內(nèi)存地址中植阴。
}
  1f:   5d                      pop    rbp
  20:   c3                      ret  

所以,我們需要保證圾浅,在內(nèi)存地址為 16 的指令讀取 rbp-0x4 里面的值之前掠手,內(nèi)存地址 12 的指令寫入到 rbp-0x4 的操作必須完成。這就是先寫后讀所面臨的數(shù)據(jù)依賴狸捕。

先讀后寫案例:

int main() {
  int a = 1;
  int b = 2;
  a = b + a;
  b = a + b;
}
===============================================================
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
   int b = 2;
   b:   c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
   a = b + a;
  12:   8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]          把內(nèi)存地址為 [rbp-0x8] 的數(shù)據(jù)寫入到寄存器 eax 中喷鸽。
  15:   01 45 fc                add    DWORD PTR [rbp-0x4],eax          把 eax 寄存器里面的值讀出來(lái),再加到 rbp-0x4 的內(nèi)存地址里灸拍。[rbp-0x4] 地址的數(shù)據(jù)值變了
   b = a + b;
  18:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]          把內(nèi)存地址為 [rbp-0x4] 的數(shù)據(jù)寫入更新到 eax 這個(gè)寄存器中做祝。
  1b:   01 45 f8                add    DWORD PTR [rbp-0x8],eax          把寄存器 eax 中的值讀出來(lái),加到內(nèi)存地址為 [rbp-0x8] 的地址處鸡岗。
}
  1e:   5d                      pop    rbp
  1f:   c3                      ret       

如果我們?cè)趦?nèi)存地址 18 的 eax 的寫入先完成了混槐,在內(nèi)存地址為 15 的代碼里面取出 eax 才發(fā)生,我們的程序計(jì)算就會(huì)出錯(cuò)轩性。第 15 行執(zhí)行的邏輯便不是 b+a 而變成了 a+a声登。

寫后再寫案例:

int main() {
  int a = 1;
  a = 2;
}
===============================================================
int main() {
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
  int a = 1;
   4:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  a = 2;
   b:   c7 45 fc 02 00 00 00    mov    DWORD PTR [rbp-0x4],0x2
}

如果內(nèi)存地址 b 的指令在內(nèi)存地址 4 的指令之后寫入。那么這些指令完成之后揣苏,rbp-0x4 里的數(shù)據(jù)就是錯(cuò)誤的悯嗓。

流水線停頓(流水線冒泡)

如果我們發(fā)現(xiàn)了后面執(zhí)行的指令,會(huì)對(duì)前面執(zhí)行的指令有數(shù)據(jù)層面的依賴關(guān)系卸察,那最簡(jiǎn)單的辦法就是“再等等”脯厨。我們?cè)谶M(jìn)行指令譯碼的時(shí)候,會(huì)拿到對(duì)應(yīng)指令所需要訪問(wèn)的寄存器和內(nèi)存地址坑质。這樣就能夠判斷出來(lái)合武,這個(gè)指令是否會(huì)觸發(fā)數(shù)據(jù)冒險(xiǎn)个少。 如果會(huì)觸發(fā)數(shù)據(jù)冒險(xiǎn),由于時(shí)鐘信號(hào)會(huì)不停地在 0 和 1 之間自動(dòng)切換眯杏,我們無(wú)法讓流水線真的停下來(lái)等等。在執(zhí)行后面的操作步驟前面壳澳,插入一個(gè) NOP 操作岂贩,
也就是執(zhí)行一個(gè)其實(shí)什么都不干的操作。可見(jiàn)巷波,這也是一個(gè)犧牲CPU性能的方法萎津,最差的情況下,流水線架構(gòu)的 CPU抹镊,又會(huì)退化成單指令周期的 CPU 了锉屈。

image

在實(shí)踐當(dāng)中,各個(gè)指令不需要的階段垮耳,并不會(huì)直接跳過(guò)颈渊,而是會(huì)運(yùn)行一次 NOP 操作。通過(guò)插入一個(gè) NOP 操作终佛,我們可以使后一條指令的每一個(gè) Stage俊嗽,一定不和前一條指令的同 Stage 在一個(gè)時(shí)鐘周期執(zhí)行。這樣铃彰,就不會(huì)發(fā)生先后兩個(gè)指令绍豁,在同一時(shí)鐘周期競(jìng)爭(zhēng)相同的資源,產(chǎn)生結(jié)構(gòu)冒險(xiǎn)了牙捉。

image

操作數(shù)前推

通過(guò) NOP 操作進(jìn)行對(duì)齊竹揍,我們?cè)诹魉€里,就不會(huì)遇到資源競(jìng)爭(zhēng)產(chǎn)生的結(jié)構(gòu)冒險(xiǎn)問(wèn)題了邪铲。-> 指令芬位,數(shù)據(jù)譯碼可以分析出,然后對(duì)應(yīng)產(chǎn)生資源競(jìng)爭(zhēng)的后置位的流水線周期中添加 NOP 操作霜浴。

插入過(guò)多的 NOP 操作晶衷,意味著我們的 CPU 總是在空轉(zhuǎn),所以應(yīng)該盡量減少一些 NOP 操作的插入阴孟。

案例:

add $t0, $s2,$s1
add $s2, $s1,$t0
  1. 第一條指令晌纫,把 s1 和 s2 寄存器里面的數(shù)據(jù)相加,存入到t0 這個(gè)寄存器里面永丝。
  2. 第二條指令锹漱,把 s1 和 t0 寄存器里面的數(shù)據(jù)相加,存入到s2 這個(gè)寄存器里面慕嚷。

這樣一段代碼可以分析出哥牍,在執(zhí)行階段會(huì)遇到數(shù)據(jù)依賴類型的冒險(xiǎn)毕泌。即后一條指令的執(zhí)行依賴前一條指令的數(shù)據(jù)寫會(huì)階段的執(zhí)行結(jié)果。 根據(jù)流水線停頓方案的處理嗅辣,我們需要在第二條指令的譯碼階段之后撼泛,插入對(duì)應(yīng)的 NOP 指令,直到前一條指令的數(shù)據(jù)寫回完成之后澡谭,才能繼續(xù)執(zhí)行愿题。這樣的弊端是浪費(fèi)了兩個(gè)時(shí)鐘周期 相當(dāng)于第二條指令多執(zhí)行了兩個(gè)時(shí)鐘周期來(lái)運(yùn)行兩次空轉(zhuǎn)的 NOP 操作。

image

操作數(shù)前推:第一條指令的執(zhí)行結(jié)果蛙奖,能夠直接傳輸給第二條指令的執(zhí)行階段作為輸入潘酗,那第二條指令,就不用再?gòu)募拇嫫骼锩嫜阒伲褦?shù)據(jù)再單獨(dú)讀出來(lái)一次仔夺,才來(lái)執(zhí)行代碼。即我們?cè)诘谝粭l指令的執(zhí)行階段完成之后攒砖,直接將結(jié)果數(shù)據(jù)傳輸給到下一條指令的 ALU缸兔。然后,下一條指令不需要再插入兩個(gè) NOP 階段吹艇,就可以繼續(xù)正常走到執(zhí)行階段灶体。

image

需要注意的是:有的時(shí)候,雖然我們可以把操作數(shù)轉(zhuǎn)發(fā)到下一條指令掐暮,但是下一條指令的執(zhí)行階段蝎抽,需要在該條指令訪存階段完成之后,才能進(jìn)行路克。 如下樟结,LOAD 指令在訪存階段才能把數(shù)據(jù)讀取出來(lái),所以下一條指令的執(zhí)行階段精算,需要在訪存階段完成之后瓢宦,才能進(jìn)行。

image

亂序執(zhí)行

因?yàn)槿缟蠄D灰羽,即便操作數(shù)前推也會(huì)遇到不得不停下整個(gè)流水線等待前置指令仿存結(jié)束驮履。還是需要整個(gè)流水線執(zhí)行一次 NOP 操作。但問(wèn)題在于廉嚼,后續(xù)沒(méi)有數(shù)據(jù)依賴的指令玫镐,也跟著前面的指令一起無(wú)謂的等待了〉≡耄可見(jiàn)無(wú)論流水線停頓還是操作數(shù)前推恐似,歸根結(jié)底還是只要前置的指令特定的流水線階段還沒(méi)有執(zhí)行完成,后面的指令就會(huì)被“阻塞”住傍念。

亂序執(zhí)行:在流水線里矫夷,后面的指令不依賴前面的指令葛闷,那就不用等待前面的指令執(zhí)行,它完全可以先執(zhí)行双藕。

案例:

a = b + c
d = a * e
x = y * z

對(duì)于流水線停頓和操作數(shù)前推淑趾,計(jì)算里面的 x ,卻要等待 a 和 d 都計(jì)算完成忧陪,實(shí)在沒(méi)啥必要治笨。所以我們完全可以在 d 的計(jì)算等待 a 的計(jì)算的過(guò)程中,先把 x 的結(jié)果計(jì)算出來(lái)赤嚼,不要讓 CPU 空轉(zhuǎn)。

image

亂序執(zhí)行的方法顺又,在第二條指令等待第一條指令的訪存階段的時(shí)候更卒,第三條指令已經(jīng)執(zhí)行完成。

引入亂序執(zhí)行后的 CPU 流水線:

image
  1. 在取指令和指令譯碼的時(shí)候稚照,亂序執(zhí)行的 CPU 和其他使用流水線架構(gòu)的 CPU 是一樣的蹂空。它會(huì)一級(jí)一級(jí)順序地進(jìn)行取指令和指令譯碼的工作。
  2. 在指令譯碼完成之后果录,就不一樣了上枕。CPU 不會(huì)直接進(jìn)行指令執(zhí)行,而是進(jìn)行一次指令分發(fā)弱恒,把指令發(fā)到一個(gè)叫作保留站 的地方辨萍。顧名思義,這個(gè)保留站返弹,就像一個(gè)火車站一樣锈玉。發(fā)送到車站的指令,就像是一列列的火車义起。
  3. 這些指令不會(huì)立刻執(zhí)行拉背,而要等待它們所依賴的數(shù)據(jù),傳遞給它們之后才會(huì)執(zhí)行默终。這就好像一列列的火車都要等到乘客來(lái)齊了才能出發(fā)椅棺。
  4. 一旦指令依賴的數(shù)據(jù)來(lái)齊了,指令及數(shù)據(jù)就可以打包并交到后面的功能單元齐蔽,其實(shí)就是 ALU两疚,去執(zhí)行了。我們有很多功能單元可以并行運(yùn)行含滴,但是不同的功能單元能夠支持執(zhí)行的指令并不相同鬼雀。
  5. 指令執(zhí)行的階段完成之后,我們并不能立刻把結(jié)果寫回到寄存器里面去蛙吏,而是把結(jié)果再存放到一個(gè)叫作重排序緩沖區(qū)的地方源哩。
  6. 在重排序緩沖區(qū)里鞋吉,我們的 CPU 會(huì)按照取指令的順序,對(duì)指令的計(jì)算結(jié)果重新排序励烦。 只有排在前面的指令都已經(jīng)完成了谓着,才會(huì)提交指令,完成整個(gè)指令的運(yùn)算結(jié)果坛掠。
  7. 實(shí)際的指令的計(jì)算結(jié)果數(shù)據(jù)赊锚,并不是直接寫到內(nèi)存或者高速緩存里,而是先寫入存儲(chǔ)緩沖區(qū)里面屉栓,最終才會(huì)寫入到高速緩存和內(nèi)存里舷蒲。

亂序執(zhí)行,只有 CPU 內(nèi)部指令的執(zhí)行層面友多,可能是“亂序”的牲平。關(guān)鍵在于指令的譯碼階段正確地分析出指令之間的數(shù)據(jù)依賴關(guān)系,來(lái)保證亂序執(zhí)行在互相沒(méi)有數(shù)據(jù)依賴影響的指令之間發(fā)生域滥。

所以案例代碼的執(zhí)行順序應(yīng)該是:x -> a -> d纵柿。或者 a启绰,x -> d昂儒。在整個(gè)計(jì)算過(guò)程中,負(fù)責(zé)乘法的功能單元都沒(méi)有閑置委可。執(zhí)行完 x 之后等來(lái)了 d渊跋,接著運(yùn)行 d。

整個(gè)亂序執(zhí)行技術(shù)着倾,就好像在指令的執(zhí)行階段提供一個(gè)“線程池”刹枉。指令不再是順序執(zhí)行的,而是根據(jù)池里所擁有的資源屈呕,以及各個(gè)任務(wù)是否可以進(jìn)行執(zhí)行微宝,進(jìn)行動(dòng)態(tài)調(diào)度。在執(zhí)行完成之后虎眨,又重新把結(jié)果在一個(gè)隊(duì)列里面蟋软,按照指令的分發(fā)順序重新排序。即使內(nèi)部是“亂序”的嗽桩,但是在外部看起來(lái)岳守,仍然是井井有條地順序執(zhí)行。

亂序執(zhí)行碌冶,極大地提高了 CPU 的運(yùn)行效率湿痢。核心原因是,現(xiàn)代 CPU 的運(yùn)行速度比訪問(wèn)主內(nèi)存的速度要快很多。如果完全采用順序執(zhí)行的方式譬重,很多時(shí)間都會(huì)浪費(fèi)在前面指令等待獲取內(nèi)存數(shù)據(jù)的時(shí)間里拒逮。CPU 不得不加入 NOP 操作進(jìn)行空轉(zhuǎn)。而現(xiàn)代 CPU 的流水線級(jí)數(shù)也已經(jīng)相對(duì)比較深了臀规,到達(dá)了 14 級(jí)滩援。這也意味著,同一個(gè)時(shí)鐘周期內(nèi)并行執(zhí)行的指令數(shù)是很多的塔嬉。

而亂序執(zhí)行玩徊,以及高速緩存,彌補(bǔ)了 CPU 和內(nèi)存之間的性能差異谨究。同樣恩袱,也充分利用了較深的流水行帶來(lái)的并發(fā)性,使得我們可以充分利用 CPU 的性能胶哲。


為什么要保障內(nèi)存訪問(wèn)的順序呢畔塔?在前后執(zhí)行的指令沒(méi)有相關(guān)數(shù)據(jù)依賴的情況下,為什么我們?nèi)匀灰筮@個(gè)順序呢纪吮?

數(shù)據(jù)從cpu --> 寄存器 --> 內(nèi)存,數(shù)據(jù)從CPU到內(nèi)存中間有個(gè)寄存器萎胰,寄存器和內(nèi)存數(shù)據(jù)交換應(yīng)該也是整頁(yè)交換碾盟,如果不順序?qū)懟丶拇嫫鞯脑挘苡锌赡茉诩拇嫫黜?yè)邊界的時(shí)候技竟,到內(nèi)存發(fā)生時(shí)間差冰肴,導(dǎo)致后面寄存器再重新取內(nèi)存的時(shí)候發(fā)生數(shù)據(jù)錯(cuò)誤,取數(shù)據(jù)的時(shí)間差內(nèi)榔组,內(nèi)存段中的數(shù)據(jù)被更改了熙尉。之前數(shù)據(jù)不依賴鸟廓,不保證后面數(shù)據(jù)不依賴驶沼。所以還是順序?qū)懟乇容^安全。

控制冒險(xiǎn)

在結(jié)構(gòu)冒險(xiǎn)和數(shù)據(jù)冒險(xiǎn)的演示案例中苇经,所有的流水線停頓操作都要從指令執(zhí)行階段開始锨推。流水線的前兩個(gè)階段铅歼,也就是取指令(IF)和指令譯碼(ID)的階段,是不需要停頓的换可。但取指令和指令譯碼不會(huì)需要遇到任何停頓椎椰,這是基于一個(gè)假設(shè)。所有的指令代碼都是順序加載執(zhí)行的沾鳄。不過(guò)這個(gè)假設(shè)慨飘,在執(zhí)行的代碼代碼中,一旦遇到 if…else 這樣的條件分支译荞,或者 for/while 循環(huán)瓤的,就會(huì)不成立休弃。

image

在 jmp 指令發(fā)生的時(shí)候,CPU 可能會(huì)跳轉(zhuǎn)去執(zhí)行其他指令堤瘤。jmp 后的那一條指令是否應(yīng)該順序加載執(zhí)行玫芦,在流水線里面進(jìn)行取指令的時(shí)候無(wú)法得知,要等 jmp 指令執(zhí)行完成本辐,去更新了 PC 寄存器之后桥帆,才能知道是否執(zhí)行下一條指令,還是跳轉(zhuǎn)到另外一個(gè)內(nèi)存地址慎皱,去取別的指令老虫。

這種為了確保能取到正確的指令,而不得不進(jìn)行等待延遲的情況茫多,就是控制冒險(xiǎn)祈匙。

縮短分支延遲

條件跳轉(zhuǎn)指令的電路操作:根據(jù)指令的 opcode 去條件碼寄存器比較,然后將跳轉(zhuǎn)的地址寫入到 PC 寄存器天揖。

但無(wú)論是 opcode夺欲,還是對(duì)應(yīng)的條件碼寄存器,還是我們跳轉(zhuǎn)的地址今膊,都是在指令譯碼(ID)的階段就能獲得的些阅。 而對(duì)應(yīng)的條件碼比較的電路,只要是簡(jiǎn)單的邏輯門電路就可以了斑唬,并不需要一個(gè)完整而復(fù)雜的 ALU市埋。所以,我們可以將條件判斷恕刘、地址跳轉(zhuǎn)缤谎,都提前到指令譯碼階段進(jìn)行,而不需要放在指令執(zhí)行階段褐着。對(duì)應(yīng)的坷澡,我們也要在 CPU 里面設(shè)計(jì)對(duì)應(yīng)的旁路,在指令譯碼階段含蓉,就提供對(duì)應(yīng)的判斷比較的電路洋访。

這種方式,本質(zhì)上和前面數(shù)據(jù)冒險(xiǎn)的操作數(shù)前推的解決方案類似谴餐,就是在硬件電路層面姻政,把一些計(jì)算結(jié)果更早地反饋到流水線中。這樣反饋?zhàn)兊酶炝似裆ぃ竺娴闹噶钚枰却臅r(shí)間就變短了汁展。不過(guò)只是改造硬件,并不能徹底解決問(wèn)題。跳轉(zhuǎn)指令的比較結(jié)果食绿,仍然要在指令執(zhí)行的時(shí)候才能知道侈咕。 在流水線里,第一條指令進(jìn)行指令譯碼的時(shí)鐘周期里器紧,我們其實(shí)就要去取下一條指令了耀销。這個(gè)時(shí)候,我們其實(shí)還沒(méi)有開始指令執(zhí)行階段铲汪,自然也就不知道比較的結(jié)果熊尉。

分支預(yù)測(cè)

簡(jiǎn)單的分支預(yù)測(cè)方式是假裝分支不發(fā)生。即指令不受判斷的影響掌腰,一直順序向下運(yùn)行狰住。條件一定不發(fā)生,這是一種靜態(tài)預(yù)測(cè)齿梁。

如果這種預(yù)測(cè)方式失敗了催植,那我們就把后面已經(jīng)取出指令已經(jīng)執(zhí)行的部分,給丟棄掉勺择。這個(gè)丟棄的操作创南,在流水線里面,叫作 Zap 或者 Flush省核。CPU 不僅要執(zhí)行后面的指令稿辙,對(duì)于這些已經(jīng)在流水線里面執(zhí)行到一半的指令,我們還需要做對(duì)應(yīng)的清除操作芳撒。比如邓深,清空已經(jīng)使用的寄存器里面的數(shù)據(jù)等等未桥,這些清除操作笔刹,也有一定的開銷。這樣CPU 需要提供對(duì)應(yīng)的丟棄指令的功能冬耿,通過(guò)控制信號(hào)清除掉已經(jīng)在流水線中執(zhí)行的指令舌菜。只要對(duì)應(yīng)的清除開銷不要太大,我們就是劃得來(lái)亦镶。

image

動(dòng)態(tài)分支預(yù)測(cè)

這個(gè)方法日月,其實(shí)就是用一個(gè)比特,去記錄當(dāng)前分支的比較情況缤骨,直接用當(dāng)前分支的比較情況爱咬,來(lái)預(yù)測(cè)下一次分支時(shí)候的比較情況。這種方法叫一級(jí)分支預(yù)測(cè)绊起,對(duì)應(yīng)的精拟,還有用兩個(gè)比特來(lái)記錄對(duì)應(yīng)的狀態(tài),需要連續(xù)兩次相同的結(jié)果才會(huì)預(yù)測(cè)下一次分支的比較情況。

案例:

public class BranchPrediction {
    public static void main(String args[]) {        
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j <1000; j ++) {
                for (int k = 0; k < 10000; k++) {
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("Time spent is " + (end - start));
                
        start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            for (int j = 0; j <1000; j ++) {
                for (int k = 0; k < 100; k++) {
                }
            }
        }
        end = System.currentTimeMillis();
        System.out.println("Time spent is " + (end - start) + "ms");
    }
}
==========================================
Time spent in first loop is 5ms
Time spent in second loop is 15ms

以上兩個(gè)循環(huán)體執(zhí)行時(shí)間的差異正式因?yàn)榉种ьA(yù)測(cè)蜂绎,循環(huán)其實(shí)也是利用 cmp 和 jle 這樣先比較后跳轉(zhuǎn)的指令來(lái)實(shí)現(xiàn)的栅表。而分支預(yù)測(cè)策略最簡(jiǎn)單的一個(gè)方式,自然是“假定分支不發(fā)生”师枣。對(duì)應(yīng)到上面的代碼怪瓶,第一段代碼的最內(nèi)層每執(zhí)行10000次才會(huì)發(fā)生一次錯(cuò)誤,而第二段代碼執(zhí)行100次就會(huì)發(fā)生一次践美。這樣的循環(huán)體累積下來(lái)洗贰,預(yù)測(cè)錯(cuò)誤的數(shù)量也是很客觀的。需要丟棄更多的運(yùn)行到一半的指令拨脉,并再去重新加載新的指令執(zhí)行哆姻。

超長(zhǎng)指令字設(shè)計(jì)

可是即便坐了如此多流水線層民的優(yōu)化,以及亂序執(zhí)行玫膀,最佳情況下矛缨,IPC 也只能到 1。因?yàn)镃PU 仍然只能在一個(gè)時(shí)鐘周期里面帖旨,取一條指令箕昭。

CPI:時(shí)鐘周期數(shù),它的倒數(shù) IPC解阅,即一個(gè)時(shí)鐘周期里面能夠執(zhí)行的指令數(shù)落竹,代表了 CPU 的吞吐率。

需要知道货抄,整數(shù)和浮點(diǎn)數(shù)計(jì)算的電路述召,在 CPU 層面是分開的。并不是所有計(jì)算功能都在一個(gè) ALU 里面蟹地,真實(shí)的情況是积暖,處理器會(huì)有多個(gè) ALU。

亂序執(zhí)行的時(shí)候執(zhí)行階段是可以并行執(zhí)行的怪与,但是取指令及指令譯碼階段卻不行夺刑。如果我們可以一次性從內(nèi)存里面取出多條指令,然后分發(fā)給多個(gè)并行的指令譯碼器分别,進(jìn)行譯碼遍愿,然后對(duì)應(yīng)交給不同的功能單元去處理。這樣耘斩,我們?cè)谝粋€(gè)時(shí)鐘周期里沼填,能夠完成的指令就不只一條了。IPC 也就能做到大于 1 了括授。

image

這種 CPU 設(shè)計(jì)坞笙,即是多發(fā)射和超標(biāo)量的CPU設(shè)計(jì)轧邪。

  • 多發(fā)射:如圖同一個(gè)時(shí)間,可能會(huì)同時(shí)把多條指令發(fā)射到不同的譯碼器或者后續(xù)處理的流水線中去羞海。
  • 超標(biāo)量:如圖有很多條并行的流水線忌愚,而不是只有一條流水線。本來(lái)我們?cè)谝粋€(gè)時(shí)鐘周期里面却邓,只能執(zhí)行一個(gè)標(biāo)量的運(yùn)算硕糊。在多發(fā)射的情況下,我們就能夠超越這個(gè)限制腊徙,同時(shí)進(jìn)行多次計(jì)算简十。即能夠讓取指令以及指令譯碼也并行進(jìn)行。
image

需要知道的是撬腾,不同的功能單元的流水線長(zhǎng)度不一樣螟蝙。我們平時(shí)所說(shuō)的14級(jí)流水線,指的通常是進(jìn)行整數(shù)計(jì)算指令的流水線長(zhǎng)度民傻。復(fù)雜的計(jì)算胰默,流水線長(zhǎng)度會(huì)更長(zhǎng)。

無(wú)論亂序執(zhí)行還是超標(biāo)量技術(shù)都需要解決前述的冒險(xiǎn)問(wèn)題漓踢。CPU 在指令執(zhí)行之前牵署,去判斷指令之間是否有依賴關(guān)系。如果有對(duì)應(yīng)的依賴關(guān)系喧半,指令就不能分發(fā)到執(zhí)行階段奴迅。可是對(duì)于依賴關(guān)系的檢測(cè)挺据,會(huì)使硬件電路更復(fù)雜取具。

程序的 CPU 執(zhí)行時(shí)間 = 指令數(shù) × CPI × Clock Cycle Time

超長(zhǎng)指令字設(shè)計(jì): 后續(xù) Intel 提出了把分析和解決指令依賴關(guān)系的事情放到了軟件層面。叫作超長(zhǎng)指令字設(shè)計(jì)即不僅想讓編譯器來(lái)優(yōu)化指令數(shù)扁耐,還想直接通過(guò)編譯器暇检,來(lái)優(yōu)化 CPI。搞定指令先后的依賴關(guān)系做葵,使得一次可以取一個(gè)指令包占哟。

在亂序執(zhí)行和超標(biāo)量的 CPU 架構(gòu)里心墅,指令的前后依賴關(guān)系酿矢,是由 CPU 內(nèi)部的硬件電路來(lái)檢測(cè)的。而到了超長(zhǎng)指令字 的架構(gòu)里面怎燥,這個(gè)工作交給了編譯器這個(gè)軟件瘫筐。

image

于是,我們可以讓編譯器把沒(méi)有依賴關(guān)系的代碼位置進(jìn)行交換铐姚。然后策肝,再把多條連續(xù)的指令打包成一個(gè)指令包肛捍。CPU 在運(yùn)行的時(shí)候,不再是取一條指令之众,而是取出一個(gè)指令包拙毫。解析出 3 條指令直接并行運(yùn)行

使用超長(zhǎng)指令字架構(gòu)的 CPU棺禾,同樣是采用流水線架構(gòu)的缀蹄。也就是說(shuō),一組指令膘婶,仍然要經(jīng)歷多個(gè)時(shí)鐘周期缺前。同樣的,下一組指令并不是等上一組指令執(zhí)行完成之后再執(zhí)行悬襟,而是在上一組指令的指令譯碼階段衅码,就開始取指令了 。值得注意的一點(diǎn)是脊岳,流水線停頓這件事情在超長(zhǎng)指令字里面逝段,很多時(shí)候也是由編譯器來(lái)做的。除了停下整個(gè)處理器流水線割捅,超長(zhǎng)指令字的 CPU 不能在某個(gè)時(shí)鐘周期停頓一下惹恃,等待前面依賴的操作執(zhí)行完成。編譯器需要在適當(dāng)?shù)奈恢貌迦?NOP 操作棺牧,直接在編譯出來(lái)的機(jī)器碼里面巫糙,就把流水線停頓這個(gè)事情在軟件層面就安排妥當(dāng)


在超長(zhǎng)指令字架構(gòu)的 CPU 下颊乘,操作數(shù)前推参淹、亂序執(zhí)行,分支預(yù)測(cè)能用在這樣的體系架構(gòu)下么乏悄?

VLIW架構(gòu)下處理器亂序執(zhí)行應(yīng)該不需要了浙值,因?yàn)榫幾g器已經(jīng)將可以并行執(zhí)行的指令打包成了指令包;操作數(shù)前推和分支預(yù)測(cè)應(yīng)該可以用檩小。

超線程

所有上面提到的提升CPU性能的方案开呐,流水線架構(gòu),分支預(yù)測(cè)规求,亂序執(zhí)行以及超標(biāo)量和超長(zhǎng)指令字節(jié)本質(zhì)上都是一種指令級(jí)并行筐付,想要通過(guò)同一時(shí)間執(zhí)行兩條指令,來(lái)提升 CPU 的吞吐率阻肿。

但這些方法都會(huì)受流水線級(jí)數(shù)太深而起不到效果瓦戚。 更深的流水線就意味著同時(shí)在流水線里面的指令就越多,相互的依賴關(guān)系就越多丛塌。所以很多時(shí)候较解,不得不把流水線停頓下來(lái)畜疾,插入很多 NOP 操作,來(lái)解決這些依賴帶來(lái)的冒險(xiǎn)問(wèn)題印衔。

超線程技術(shù): 既然 CPU 同時(shí)運(yùn)行那些在代碼層面有前后依賴關(guān)系的指令啡捶,會(huì)遇到各種冒險(xiǎn)問(wèn)題,不如去找一些和這些指令完全獨(dú)立奸焙,沒(méi)有依賴關(guān)系的指令來(lái)運(yùn)行好了届慈。 -> 這樣的指令來(lái)自同時(shí)運(yùn)行的另外一個(gè)程序里

超線程的 CPU,其實(shí)是把一個(gè)物理層面 CPU 核心忿偷,“偽裝”成兩個(gè)邏輯層面的 CPU 核心金顿。這個(gè) CPU,會(huì)在硬件層面增加很多電路鲤桥,使得我們可以在一個(gè) CPU 核心內(nèi)部揍拆,維護(hù)兩個(gè)不同線程的指令的狀態(tài)信息。

image

如圖茶凳,在一個(gè)物理 CPU 核心內(nèi)部嫂拴,會(huì)有雙份的 PC 寄存器、指令寄存器乃至條件碼寄存器贮喧。這樣筒狠,這個(gè) CPU 核心就可以維護(hù)兩條并行的指令的狀態(tài)。 在外面看起來(lái)箱沦,似乎有兩個(gè)邏輯層面的 CPU 在同時(shí)運(yùn)行辩恼,但在 CPU 的其他功能組件上,Intel 還是只提供了一份谓形,如指令譯碼器和 ALU灶伊,一個(gè) CPU 核心仍然只有一份。因?yàn)槌€程并不是真的去支持同時(shí)運(yùn)行==任意==兩個(gè)指令寒跳,那就真的變成物理多核了聘萨,在超線程基礎(chǔ)上并行的指令,需要兩個(gè)線程同時(shí)不沖突的使用CPU的資源

B8E58B11-7659-4364-8228-02A0661EA9A1.png

超線程的目的:是在一個(gè)線程 A 的指令童太,在流水線里停頓的時(shí)候米辐,讓另外一個(gè)線程去執(zhí)行指令。 因?yàn)檫@個(gè)時(shí)候书释,CPU 的譯碼器和 ALU 就空出來(lái)了翘贮,那么另外一個(gè)線程 B,就可以拿來(lái)干自己需要的事情征冷。這個(gè)線程 B 可沒(méi)有對(duì)于線程 A 里面指令的關(guān)聯(lián)和依賴择膝。

這樣誓琼,CPU 通過(guò)很小的代價(jià)检激,就能實(shí)現(xiàn)“同時(shí)”運(yùn)行多個(gè)線程的效果肴捉。通常只要在CPU核心的添加10%左右的邏輯功能,增加可以忽略不計(jì)的晶體管數(shù)量叔收,就能做到這一點(diǎn)齿穗。由于并沒(méi)有增加真的功能單元。所以超線程只在特定的應(yīng)用場(chǎng)景下效果比較好饺律。一般是在那些各個(gè)線程“等待”時(shí)間比較長(zhǎng)的應(yīng)用場(chǎng)景下窃页。比如,需要應(yīng)對(duì)很多請(qǐng)求的數(shù)據(jù)庫(kù)應(yīng)用复濒,就很適合使用超線程脖卖。各個(gè)指令都要等待訪問(wèn)內(nèi)存數(shù)據(jù),但是并不需要做太多計(jì)算巧颈。后置指令需要依賴前置指令的內(nèi)存訪問(wèn)才能執(zhí)行畦木,這個(gè)等待階段就可以換其他數(shù)據(jù)不相關(guān)線程執(zhí)行。

單指令多數(shù)據(jù)流 SIMD

SIMD性能差異案例砸泛,如下兩段代碼的執(zhí)行時(shí)間十籍,使用SIMD技術(shù)的性能是未使用的30多倍。

python
>>> import numpy as np
>>> import timeit
>>> a = list(range(1000))
>>> b = np.array(range(1000))
>>> timeit.timeit("[i + 1 for i in a]", setup="from __main__ import a", number=1000000)
32.82800309999993
>>> timeit.timeit("np.add(1, b)", setup="from __main__ import np, b", number=1000000)
0.9787889999997788
>>>

前者的計(jì)算方式被稱為SISD唇礁,即單指令單數(shù)據(jù)勾栗。后者即是單指令多數(shù)據(jù)的 SIMD,而一個(gè)多核 CPU盏筐,它同時(shí)處理多個(gè)指令的方式可以叫作MIMD围俘,也就是多指令多數(shù)據(jù)。

SIMD 在獲取數(shù)據(jù)和執(zhí)行指令的時(shí)候琢融,都做到了并行楷拳。在從內(nèi)存里面讀取數(shù)據(jù)的時(shí)候,SIMD 是一次性讀取多個(gè)數(shù)據(jù)吏奸。 ==Intel 在引入 SSE 指令集的時(shí)候==欢揖,在 CPU 里面添上了 8 個(gè) 128 Bits 的寄存器。128 Bits 也就是 16 Bytes 奋蔚,也就是說(shuō)她混,一個(gè)寄存器一次性可以加載 4 個(gè)整數(shù)。比起循環(huán)分別讀取 4 次對(duì)應(yīng)的數(shù)據(jù)泊碑,時(shí)間就省下來(lái)了坤按。

image

在數(shù)據(jù)讀取到了之后,在指令的執(zhí)行層面馒过,SIMD 也是可以并行進(jìn)行的臭脓。 4 個(gè)整數(shù)各自加 1,互相之前完全沒(méi)有依賴腹忽,也就沒(méi)有冒險(xiǎn)問(wèn)題需要處理来累。只要 CPU 里有足夠多的功能單元砚作,能夠同時(shí)進(jìn)行這些計(jì)算,這個(gè)加法就是 4 路同時(shí)并行的嘹锁,自然也省下了時(shí)間伪很。

總結(jié)

指令級(jí)并行是一種隱式并行瘟滨,也就是寫程序的人不需要關(guān)注,通過(guò)流水線和超標(biāo)量,使得一個(gè)程序的指令序列中有多條同時(shí)亂序運(yùn)行宙项,順序提交泣矛。這依賴寄存器重命名真慢,多個(gè)執(zhí)行單元灾挨,重排序緩沖和指令預(yù)測(cè)技術(shù)。

線程級(jí)并行時(shí)一種顯式并行继低,也就是程序員要寫多線程程序但金。線程級(jí)并行主要指同時(shí)多線程(SMT)/超線程(HT)以及多核和多處理器。SMT是在指令級(jí)并行的基礎(chǔ)上的擴(kuò)展郁季,可以在一個(gè)核上運(yùn)行多個(gè)線程冷溃,多個(gè)線程共享執(zhí)行單元,以便提高部件的利用率梦裂,提高吞吐量似枕。SMT需要為每個(gè)線程單獨(dú)保持狀態(tài),如程序計(jì)數(shù)器(PC)年柠,寄存器堆凿歼,重排序緩沖等。

數(shù)據(jù)級(jí)并行是一種顯式并行冗恨,主要指單指令多數(shù)據(jù)(SIMD)答憔,比如a,b和c都是相同大小的數(shù)組掀抹,要進(jìn)行的計(jì)算是a的每一個(gè)元素與b的響應(yīng)元素進(jìn)行運(yùn)算虐拓,結(jié)果放入c的對(duì)應(yīng)元素中。如果沒(méi)有SIMD傲武,就需要寫一個(gè)循環(huán)執(zhí)行多遍來(lái)完成蓉驹,而SIMD中 一條指令就可以并行地執(zhí)行運(yùn)算。

異常和中斷

常見(jiàn)異常

  • 硬件層面:當(dāng)加法器進(jìn)行兩個(gè)數(shù)相加的時(shí)候揪利,會(huì)遇到算術(shù)溢出态兴;在玩游戲的時(shí)候,按下鍵盤發(fā)送了一個(gè)信號(hào)給到 CPU疟位,CPU 要去執(zhí)行一個(gè)現(xiàn)有流程之外的指令瞻润,這也是一個(gè)“異常”。
  • 軟件層面:程序進(jìn)行系統(tǒng)調(diào)用绍撞,發(fā)起一個(gè)讀文件的請(qǐng)求正勒。這樣應(yīng)用程序向系統(tǒng)調(diào)用發(fā)起請(qǐng)求的情況,一樣是通過(guò)“異吵纾”來(lái)實(shí)現(xiàn)的昭齐。

異常尿招,是一個(gè)硬件和軟件組合到一起的處理過(guò)程矾柜。異常的發(fā)生和捕捉是在硬件層面完成的。但是異常的處理就谜,是由軟件來(lái)完成的怪蔑。

計(jì)算機(jī)會(huì)為每一種可能會(huì)發(fā)生的異常,分配一個(gè)異常代碼(中斷向量)丧荐。異常發(fā)生的時(shí)候缆瓣,通常是 CPU 檢測(cè)到了一個(gè)特殊的信號(hào)。 比如虹统,你按下鍵盤上的按鍵弓坞,輸入設(shè)備就會(huì)給 CPU 發(fā)一個(gè)信號(hào)〕道螅或者渡冻,正在執(zhí)行的指令發(fā)生了加法溢出,同樣忧便,會(huì)有一個(gè)進(jìn)位溢出的信號(hào)族吻。這些信號(hào),在組成原理里面珠增,一般叫作發(fā)生了一個(gè)事件超歌。CPU在檢測(cè)到事件的時(shí)候,其實(shí)也就拿到了對(duì)應(yīng)的異常代碼蒂教。

這些異常代碼里巍举,I/O 發(fā)出的信號(hào)的異常代碼,是由操作系統(tǒng)來(lái)分配的凝垛,也就是由軟件來(lái)設(shè)定的禀综。而像加法溢出這樣的異常代碼,則是由 CPU 預(yù)先分配好的苔严,也就是由硬件來(lái)分配的定枷。這又是另一個(gè)軟件和硬件共同組合來(lái)處理異常的過(guò)程。

拿到異常代碼之后届氢,CPU 就會(huì)觸發(fā)異常處理的流程欠窒。計(jì)算機(jī)在內(nèi)存里,會(huì)保留一個(gè)異常表。也叫中斷向量表岖妄,和上面的中斷向量對(duì)應(yīng)起來(lái)型将。這個(gè)異常表類似GOT表,存放的是不同的異常代碼對(duì)應(yīng)的異常處理程序所在的地址荐虐。

CPU 在拿到了異常碼之后七兜,會(huì)先把當(dāng)前的程序執(zhí)行的現(xiàn)場(chǎng),保存到程序棧里面福扬,然后根據(jù)異常碼查詢腕铸,找到對(duì)應(yīng)的異常處理程序,最后把后續(xù)指令執(zhí)行的指揮權(quán)铛碑,交給這個(gè)異常處理程序狠裹。

image

異常的分類:中斷、陷阱汽烦、故障和中止涛菠。

  1. 中斷:程序在執(zhí)行到一半的時(shí)候,被打斷了撇吞。這個(gè)打斷執(zhí)行的信號(hào)俗冻,來(lái)自于 CPU 外部的 I/O 設(shè)備。在鍵盤上按下一個(gè)按鍵牍颈,就會(huì)對(duì)應(yīng)觸發(fā)一個(gè)相應(yīng)的信號(hào)到達(dá) CPU 里面迄薄。CPU 里面某個(gè)開關(guān)的值發(fā)生了變化,也就觸發(fā)了一個(gè)中斷類型的異常颂砸。
  2. 陷阱:是程序員“故意“主動(dòng)觸發(fā)的異常噪奄。 如在程序里面打了一個(gè)斷點(diǎn),這個(gè)斷點(diǎn)就是設(shè)下的一個(gè)"陷阱"人乓。當(dāng)程序的指令執(zhí)行到這個(gè)位置的時(shí)候勤篮,就掉到了這個(gè)陷阱當(dāng)中。然后色罚,對(duì)應(yīng)的異常處理程序就會(huì)來(lái)處理這個(gè)"陷阱"當(dāng)中的獵物碰缔。case:應(yīng)用程序通過(guò)系統(tǒng)調(diào)用去讀取文件、創(chuàng)建進(jìn)程戳护,其實(shí)也是通過(guò)觸發(fā)一次陷阱來(lái)進(jìn)行的金抡。這是因?yàn)椋覀冇脩魬B(tài)的應(yīng)用程序沒(méi)有權(quán)限來(lái)做這些事情腌且,需要把對(duì)應(yīng)的流程轉(zhuǎn)交給有權(quán)限的異常處理程序來(lái)進(jìn)行梗肝。
  3. 故障:它和陷阱的區(qū)別在于,陷阱是我們開發(fā)程序的時(shí)候刻意觸發(fā)的異常铺董,而故障通常不是巫击。比如,我們?cè)诔绦驁?zhí)行的過(guò)程中,進(jìn)行加法計(jì)算發(fā)生了溢出坝锰,其實(shí)就是故障類型的異常粹懒。這個(gè)異常不是我們?cè)陂_發(fā)的時(shí)候計(jì)劃內(nèi)的,也一樣需要有對(duì)應(yīng)的異常處理程序去處理顷级。
  4. 終止:可以視作故障的一種特殊情況凫乖。當(dāng) CPU 遇到了故障,但是恢復(fù)不過(guò)來(lái)的時(shí)候弓颈,程序就不得不中止了帽芽。

故障和陷阱、中斷的一個(gè)重要區(qū)別是恨豁,故障在異常程序處理完成之后嚣镜,仍然回來(lái)處理當(dāng)前的指令爬迟,而不是去執(zhí)行程序中的下一條指令橘蜜。因?yàn)楫?dāng)前的指令因?yàn)楣收系脑虿](méi)有成功執(zhí)行完成

image

在這四種異常里付呕,中斷異常的信號(hào)來(lái)自系統(tǒng)外部计福,而不是在程序自己執(zhí)行的過(guò)程中,所以稱之為“異步”類型的異常徽职。而陷阱象颖、故障以及中止類型的異常,是在程序執(zhí)行的過(guò)程中發(fā)生的姆钉,所以稱之為“同步“類型的異常说订。

在實(shí)際的異常處理程序執(zhí)行之前,CPU 需要去做一次“保存現(xiàn)場(chǎng)”的操作潮瓶,即把當(dāng)前正在執(zhí)行的函數(shù)指令去壓棧陶冷。特別的還要特別處理

  • 中斷、故障 發(fā)生的時(shí)候毯辅,需要把 CPU 內(nèi)當(dāng)前運(yùn)行程序用到的所有寄存器的數(shù)據(jù)埂伦,都放到棧里面,最典型的就是條件碼寄存器里面的內(nèi)容思恐。
  • 陷阱這樣的異常沾谜,涉及程序指令在用戶態(tài)和內(nèi)核態(tài)之間的切換。對(duì)應(yīng)壓棧的時(shí)候胀莹,對(duì)應(yīng)的數(shù)據(jù)是壓到內(nèi)核棧里基跑,而不是程序棧里。
  • 故障這樣的異常描焰,在異常處理程序執(zhí)行完成之后媳否。從棧里返回出來(lái),繼續(xù)執(zhí)行的不是順序的下一條指令,而是故障發(fā)生的當(dāng)前指令逆日。因?yàn)楫?dāng)前指令因?yàn)楣收蠜](méi)有正常執(zhí)行成功嵌巷,必須重新去執(zhí)行一次。

所以室抽,對(duì)于異常這樣的處理流程搪哪,不像是順序執(zhí)行的指令間的函數(shù)調(diào)用關(guān)系。而是更像兩個(gè)不同的獨(dú)立進(jìn)程之間在 CPU 層面的切換坪圾,所以這個(gè)過(guò)程我們稱之為上下文切換晓折。

CISC RISC

image

過(guò)去為了少用內(nèi)存,指令的長(zhǎng)度也是可變的兽泄。常用的指令要短一些漓概,不常用的指令可以長(zhǎng)一些。那個(gè)時(shí)候的計(jì)算機(jī)病梢,想要用盡可能少的內(nèi)存空間胃珍,存儲(chǔ)盡量多的指令。

但后來(lái)計(jì)算機(jī)性能蓬勃發(fā)展蜓陌,存儲(chǔ)空間也更大了觅彰。并且當(dāng)時(shí)發(fā)現(xiàn)實(shí)際在 CPU 運(yùn)行的程序里,80% 的時(shí)間都是在使用 20% 的簡(jiǎn)單指令钮热。后來(lái)就提出了 RISC 的理念填抬。

RISC:CPU 選擇把指令“精簡(jiǎn)”到 20% 的簡(jiǎn)單指令。而原先的復(fù)雜指令隧期,則通過(guò)用簡(jiǎn)單指令組合起來(lái)來(lái)實(shí)現(xiàn)飒责,讓軟件來(lái)實(shí)現(xiàn)硬件的功能。這樣仆潮,CPU 的整個(gè)硬件設(shè)計(jì)就會(huì)變得更簡(jiǎn)單了宏蛉,在硬件層面提升性能也會(huì)變得更容易了。

過(guò)去因?yàn)橹噶顢?shù)量多鸵闪,軟硬件兩方面都受到了很多挑戰(zhàn)檐晕。

  • 硬件層面,要想支持更多的復(fù)雜指令蚌讼,CPU里面的電路就要更復(fù)雜辟灰,設(shè)計(jì)起來(lái)也就更困難。更復(fù)雜的電路篡石,在散熱和功耗層面芥喇,也會(huì)帶來(lái)更大的挑戰(zhàn)。
  • 軟件層面凰萨,支持更多的復(fù)雜指令继控,編譯器的優(yōu)化就變得更困難械馆。畢竟,面向 2000 個(gè)指令來(lái)優(yōu)化編譯器和面向 500 個(gè)指令來(lái)優(yōu)化編譯器的困難是完全不同的武通。

RISC 的 CPU 里完成指令的電路變得簡(jiǎn)單了霹崎,騰出了更多的空間被拿來(lái)放通用寄存器。因?yàn)?strong>RISC 完成同樣的功能冶忱,執(zhí)行的指令數(shù)量要比 CISC 多 尾菇,所以,如果需要反復(fù)從內(nèi)存里面讀取指令或者數(shù)據(jù)到寄存器里來(lái)囚枪,那么很多時(shí)間就會(huì)花在訪問(wèn)內(nèi)存上派诬。于是,RISC 架構(gòu)的 CPU 往往就有更多的通用寄存器链沼。除了寄存器這樣的存儲(chǔ)空間默赂,RISC 的 CPU 也可以把更多的晶體管,用來(lái)實(shí)現(xiàn)更好的分支預(yù)測(cè)等相關(guān)功能括勺,進(jìn)一步去提升 CPU 實(shí)際的執(zhí)行效率缆八。

程序的 CPU 執(zhí)行時(shí)間 = 指令數(shù) × CPI × Clock Cycle Time

對(duì)于這個(gè)公式,CISC 優(yōu)化的是指令數(shù)朝刊,來(lái)減少CPU的執(zhí)行時(shí)間耀里。而RISC優(yōu)化的是CPI蜈缤,因?yàn)橹噶畋容^簡(jiǎn)單拾氓,時(shí)鐘周期數(shù)也就比較少。

后來(lái) Intel 在開發(fā)安騰處理器的同時(shí)底哥,也在不斷借鑒其他 RISC 處理器的設(shè)計(jì)思想咙鞍。由于必須向前兼容,所以兼容的 x86 指令集無(wú)法更改趾徽,為了讓CISC風(fēng)格的指令集用RISC的形勢(shì)在CPU中運(yùn)行续滋,引入了微指令架構(gòu)。

image

在微指令架構(gòu)的 CPU 里面孵奶,編譯器編譯出來(lái)的機(jī)器碼和匯編代碼并沒(méi)有發(fā)生什么變化疲酌。但在指令譯碼的階段,指令譯碼器“翻譯”出來(lái)的了袁,不再是某一條 CPU 指令朗恳。譯碼器會(huì)把一條機(jī)器碼,==“翻譯”成好幾條“微指令”==载绿。這里的一條條微指令粥诫,就不再是 CISC 風(fēng)格的了,而是變成了固定長(zhǎng)度的 RISC 風(fēng)格的了崭庸。

這些 RISC 風(fēng)格的微指令怀浆,會(huì)被放到一個(gè)微指令緩沖區(qū)里面谊囚,然后再?gòu)木彌_區(qū)里面,分發(fā)給到后面的超標(biāo)量执赡,并且是亂序執(zhí)行的流水線架構(gòu)里面镰踏。不過(guò)這個(gè)流水線架構(gòu)里面接受的,就不是復(fù)雜的指令沙合,而是精簡(jiǎn)的指令了余境。在這個(gè)架構(gòu)里,我們的指令譯碼器相當(dāng)于變成了設(shè)計(jì)模式里的一個(gè)“適配器”灌诅。這個(gè)適配器芳来,填平了 CISC 和 RISC 之間的指令差異。但是這樣的譯碼器猜拾,意味著需要更復(fù)雜的電路和更長(zhǎng)的譯碼時(shí)間即舌,通過(guò) RISC 提升的性能又被譯碼器花費(fèi)了

后來(lái)挎袜,由于運(yùn)算指令的集中性20%顽聂。Intel 就在 CPU 里面加了一層 L0 Cache。這個(gè) Cache 保存的就是指令譯碼器把 CISC 的指令“翻譯”成 RISC 的微指令的結(jié)果盯仪。 于是紊搪,在大部分情況下,CPU 都可以從 Cache 里面拿到譯碼結(jié)果全景,而==不需要讓譯碼器去進(jìn)行實(shí)際的譯碼操作==耀石。這樣不僅優(yōu)化了性能,因?yàn)樽g碼器的晶體管開關(guān)動(dòng)作變少了爸黄,還減少了功耗滞伟。

因?yàn)椤拔⒅噶睢奔軜?gòu)的存在,從 Pentium Pro 開始炕贵,Intel 處理器已經(jīng)不是一個(gè)純粹的 CISC 處理器了梆奈。它同樣融合了大量 RISC 類型的處理器設(shè)計(jì)。不過(guò)称开,由于 Intel 本身在 CPU 層面做的大量?jī)?yōu)化亩钟,比如亂序執(zhí)行、分支預(yù)測(cè)等相關(guān)工作鳖轰,x86 的 CPU 始終在功耗上還是要遠(yuǎn)遠(yuǎn)超過(guò) RISC 架構(gòu)的 ARM清酥,所以最終在智能手機(jī)崛起替代 PC 的時(shí)代,落在了 ARM 后面脆霎。

現(xiàn)在总处,CISC 和 RISC 架構(gòu)的分界已經(jīng)沒(méi)有那么明顯了。Intel 和 AMD 的 CPU 也都是采用譯碼成 RISC 風(fēng)格的微指令來(lái)運(yùn)行睛蛛。而 ARM 的芯片鹦马,一條指令同樣需要多個(gè)時(shí)鐘周期胧谈,有亂序執(zhí)行和多發(fā)射。

ARM 真正能夠戰(zhàn)勝 Intel荸频,主要是因?yàn)橄旅孢@兩點(diǎn)原因

  1. 功耗優(yōu)先的設(shè)計(jì)菱肖。一個(gè) 4 核的 Intel i7 的 CPU,設(shè)計(jì)的時(shí)候功率就是 130W旭从。而一塊 ARM A8 的單個(gè)核心的 CPU稳强,設(shè)計(jì)功率只有 2W。兩者之間差出了 100 倍和悦。在移動(dòng)設(shè)備上退疫,功耗是一個(gè)遠(yuǎn)比性能更重要的指標(biāo),畢竟我們不能隨時(shí)在身上帶個(gè)發(fā)電機(jī)鸽素。ARM 的 CPU褒繁,主頻更低,晶體管更少馍忽,高速緩存更小棒坏,亂序執(zhí)行的能力更弱。所有這些遭笋,都是為了功耗所做的妥協(xié)坝冕。
  2. 低價(jià)ARM 并沒(méi)有自己壟斷 CPU 的生產(chǎn)和制造瓦呼,只是進(jìn)行 CPU 設(shè)計(jì)喂窟,然后把對(duì)應(yīng)的知識(shí)產(chǎn)權(quán)授權(quán)出去,讓其他的廠商來(lái)生產(chǎn) ARM 架構(gòu)的 CPU吵血。它甚至還允許這些廠商可以基于 ARM 的架構(gòu)和指令集谎替,設(shè)計(jì)屬于自己的 CPU。像蘋果蹋辅、三星、華為挫掏,它們都是拿到了基于 ARM 體系架構(gòu)設(shè)計(jì)和制造 CPU 的授權(quán)侦另。ARM 自己只是收取對(duì)應(yīng)的專利授權(quán)費(fèi)用。多個(gè)廠商之間的競(jìng)爭(zhēng)尉共,使得 ARM 的芯片在市場(chǎng)上價(jià)格很便宜褒傅。所以,盡管 ARM 的芯片的出貨量遠(yuǎn)大于 Intel袄友,但是收入和利潤(rùn)卻比不上 Intel殿托。

總結(jié)

RISC 的指令是固定長(zhǎng)度的,CISC 的指令是可變長(zhǎng)度的剧蚣。RISC 的指令集里的指令數(shù)少支竹,而且單個(gè)指令只完成簡(jiǎn)單的功能旋廷,所以被稱為“精簡(jiǎn)”。CISC 里的指令數(shù)多礼搁,為了節(jié)約內(nèi)存饶碘,直接在硬件層面能夠完成復(fù)雜的功能,所以被稱為“復(fù)雜”馒吴。RISC 的通過(guò)減少 CPI 來(lái)提升性能扎运,而 CISC 通過(guò)減少需要的指令數(shù)來(lái)提升性能。

Intel 的 x86 CPU 的“微指令”的設(shè)計(jì)思路饮戳±颊洌“微指令”使得我們?cè)跈C(jī)器碼層面保留了 CISC 風(fēng)格的 x86 架構(gòu)的指令集。但是关翎,通過(guò)指令譯碼器和 L0 緩存的組合哨苛,使得這些指令可以快速翻譯成 RISC 風(fēng)格的微指令,使得實(shí)際執(zhí)行指令的流水線可以用 RISC 的架構(gòu)來(lái)搭建篮赢。使用“微指令”設(shè)計(jì)思路的 CPU齿椅,不能再稱之為 CISC 了,而更像一個(gè) RISC 和 CISC 融合的產(chǎn)物启泣。

GPU

現(xiàn)在我們電腦里面顯示出來(lái)的 3D 的畫面涣脚,其實(shí)是通過(guò)多邊形組合出來(lái)的。而實(shí)際這些人物在畫面里面的移動(dòng)寥茫、動(dòng)作遣蚀,都是通過(guò)計(jì)算機(jī)根據(jù)圖形學(xué)的各種計(jì)算,實(shí)時(shí)渲染出來(lái)的纱耻。

圖形渲染的流程

圖形渲染流程:

  1. 頂點(diǎn)處理
  2. 圖元處理
  3. 柵格化
  4. 片段處理
  5. 像素操作

頂點(diǎn)處理:構(gòu)成多邊形建模的每一個(gè)多邊形芭梯,都有多個(gè)頂點(diǎn)。這些頂點(diǎn)都有一個(gè)在三維空間里的坐標(biāo)弄喘。但是我們的屏幕是二維的玖喘,所以在確定當(dāng)前視角的時(shí)候,我們需要把這些頂點(diǎn)在三維空間里面的位置蘑志,轉(zhuǎn)化到屏幕這個(gè)二維空間里面累奈。這個(gè)轉(zhuǎn)換的操作,就被叫作頂點(diǎn)處理急但。而且澎媒,這里面每一個(gè)頂點(diǎn)位置的轉(zhuǎn)換,互相之間沒(méi)有依賴波桩,是可以并行獨(dú)立計(jì)算的戒努。

image

圖元處理:圖元處理,其實(shí)就是要把頂點(diǎn)處理完成之后的各個(gè)頂點(diǎn)連起來(lái)镐躲,變成多邊形储玫。其實(shí)轉(zhuǎn)化后的頂點(diǎn)侍筛,仍然是在一個(gè)三維空間里,只是第三維的 Z 軸缘缚,是正對(duì)屏幕的“深度”勾笆。所以我們針對(duì)這些多邊形,需要做一個(gè)操作桥滨,叫剔除和裁剪窝爪,也就是把不在屏幕里面,或者一部分不在屏幕里面的內(nèi)容給去掉齐媒,減少接下來(lái)流程的工作量蒲每。

image

柵格化:我們的屏幕分辨率是有限的。它一般是通過(guò)一個(gè)個(gè)“像素”來(lái)顯示出內(nèi)容的喻括。所以邀杏,這個(gè)操作就是把多邊形轉(zhuǎn)換成屏幕里面的一個(gè)個(gè)像素點(diǎn)。這個(gè)操作呢唬血,就叫作柵格化望蜡。這個(gè)柵格化操作,有一個(gè)特點(diǎn)和上面的頂點(diǎn)處理是一樣的拷恨,就是每一個(gè)圖元都可以并行獨(dú)立地柵格化脖律。

image

片段處理:在柵格化變成了像素點(diǎn)之后,腕侄、圖還是“黑白”的小泉。我們還需要計(jì)算每一個(gè)像素的顏色、透明度等信息冕杠,給像素點(diǎn)上色微姊。這步操作,就是片段處理分预。這步操作兢交,同樣也可以每個(gè)片段并行、獨(dú)立進(jìn)行噪舀,和上面的頂點(diǎn)處理和柵格化一樣魁淳。

image

像素操作:最后一步呢,我們就要把不同的多邊形的像素點(diǎn)“混合”到一起与倡。可能前面的多邊形可能是半透明的昆稿,那么前后的顏色就要混合在一起變成一個(gè)新的顏色纺座;或者前面的多邊形遮擋住了后面的多邊形,那么我們只要顯示前面多邊形的顏色就好了溉潭。最終净响,輸出到顯示設(shè)備少欺。

image

這樣的5 個(gè)步驟的渲染流程呢,一般也被稱之為圖形流水線馋贤。

image

由于圖形渲染的流程是固定的赞别,所以直接用硬件來(lái)處理這部分過(guò)程,顯然比使用 CPU 來(lái)計(jì)算好很多配乓。因?yàn)檎麄€(gè)計(jì)算流程是完全固定的仿滔,不需要流水線停頓、亂序執(zhí)行等等的各類導(dǎo)致 CPU 計(jì)算變得復(fù)雜的問(wèn)題犹芹。我們也不需要有什么可編程能力崎页,不用像 CPU 那樣考慮計(jì)算和處理能力的通用性,只要讓硬件按照寫好的邏輯進(jìn)行運(yùn)算就好了腰埂。

image

可編程圖形處理器

因?yàn)檎麄€(gè)圖形渲染過(guò)程都是在硬件里面固定的管線來(lái)完成的飒焦。程序員們過(guò)去在加速卡上能做的事情呢,只有改配置來(lái)實(shí)現(xiàn)不同的圖形渲染效果屿笼。如果通過(guò)改配置做不到牺荠,就沒(méi)有什么辦法了。

GPU的這個(gè)編程能力不是像 CPU 那樣驴一,有非常通用的指令休雌,可以進(jìn)行任何你希望的操作,而是在整個(gè)的渲染管線的一些特別步驟蛔趴,能夠自己去定義處理數(shù)據(jù)的算法或者操作挑辆。

image

早期的可編程管線的 GPU,僅提供了單獨(dú)的頂點(diǎn)處理和片段處理(像素處理)的著色器孝情。后來(lái)由于這樣設(shè)計(jì)的電路有一半時(shí)間是閑著的鱼蝉,于是后來(lái)就有了統(tǒng)一著色器架構(gòu)。這些可以編程的接口箫荡,我們稱之為Shader中文名稱就是著色器魁亦。

因?yàn)橛玫闹噶罴且粯拥模跃驮?GPU 里面放很多個(gè)一樣的 Shader 硬件電路羔挡,然后通過(guò)統(tǒng)一調(diào)度洁奈,把頂點(diǎn)處理、圖元處理绞灼、片段處理這些任務(wù)利术,都交給這些 Shader 去處理,讓整個(gè) GPU 盡可能地忙起來(lái)低矮。這樣的設(shè)計(jì)印叁,就是我們現(xiàn)代 GPU 的設(shè)計(jì),就是統(tǒng)一著色器架構(gòu)。這個(gè)時(shí)候的“著色器”的作用轮蜕,其實(shí)已經(jīng)和它的名字關(guān)系不大了昨悼,而是變成了一個(gè)通用的抽象計(jì)算模塊的名字。

image

現(xiàn)代 GPU 的三個(gè)核心創(chuàng)意

  1. 芯片瘦身:現(xiàn)代 CPU 里的晶體管變得越來(lái)越多跃洛,越來(lái)越復(fù)雜率触,其實(shí)已經(jīng)不是用來(lái)實(shí)現(xiàn)“計(jì)算”這個(gè)核心功能,而是拿來(lái)實(shí)現(xiàn)處理亂序執(zhí)行汇竭、進(jìn)行分支預(yù)測(cè)葱蝗,以及我們之后要在存儲(chǔ)器講的高速緩存部分。

而在 GPU 里韩玩,這些電路就顯得有點(diǎn)多余了垒玲,GPU 的整個(gè)處理過(guò)程是一個(gè)流式處理的過(guò)程,因?yàn)闆](méi)有那么多分支條件找颓,或者復(fù)雜的依賴關(guān)系合愈,我們可以把 GPU 里這些對(duì)應(yīng)的電路都可以去掉,做一次小小的瘦身击狮,只留下取指令佛析、指令譯碼、ALU 以及執(zhí)行這些計(jì)算需要的寄存器和緩存就好了彪蓬。一般來(lái)說(shuō)寸莫,我們會(huì)把這些電路抽象成三個(gè)部分,就是下面圖里的取指令和指令譯碼档冬、ALU 和執(zhí)行上下文膘茎。

image
  1. 多核并行和 SIMT:這樣一來(lái),GPU 電路就比 CPU 簡(jiǎn)單很多了酷誓。于是披坏,我們就可以在一個(gè) GPU 里面,塞很多個(gè)這樣并行的 GPU 電路來(lái)實(shí)現(xiàn)計(jì)算盐数,就好像 CPU 里面的多核 CPU 一樣棒拂。和 CPU 不同的是,我們不需要單獨(dú)去實(shí)現(xiàn)什么多線程的計(jì)算玫氢。因?yàn)?GPU 的運(yùn)算是天然并行的帚屉,上面有說(shuō)。
image

SIMD:這個(gè)技術(shù)是說(shuō)漾峡,在做向量計(jì)算的時(shí)候攻旦,我們要執(zhí)行的指令是一樣的,只是同一個(gè)指令的數(shù)據(jù)有所不同而已生逸。在 GPU 的渲染管線里敬特,這個(gè)技術(shù)可就大有用處了掰邢。

GPU 就借鑒了 CPU 里面的 SIMD牺陶,用了一種叫作SIMT的技術(shù)伟阔,SIMT 比 SIMD 更加靈活。在 SIMD 里面掰伸,CPU 一次性取出了固定長(zhǎng)度的多個(gè)數(shù)據(jù)皱炉,放到寄存器里面,用一個(gè)指令去執(zhí)行狮鸭。而 SIMT合搅,可以把多條數(shù)據(jù),交給不同的線程去處理歧蕉。

由于各個(gè)線程里面執(zhí)行的指令流程是一樣的灾部,但是可能根據(jù)數(shù)據(jù)的不同,走到不同的條件分支惯退。這樣赌髓,相同的代碼和相同的流程,可能執(zhí)行不同的具體的指令催跪。于是锁蠕,GPU 設(shè)計(jì)就可以進(jìn)一步進(jìn)化,也就是在取指令和指令譯碼的階段懊蒸,取出的指令可以給到后面多個(gè)不同的 ALU 并行進(jìn)行運(yùn)算荣倾。這樣,我們的一個(gè) GPU 的核里骑丸,就可以放下更多的 ALU舌仍,同時(shí)進(jìn)行更多的并行運(yùn)算了。 并行上并行通危,超級(jí)并行- -

image

GPU中超線程

當(dāng)GPU中遇到類似CPU中的“流水線停頓”問(wèn)題铸豁。GPU 上可以和CPU一樣調(diào)度一些別的計(jì)算任務(wù)給當(dāng)前的 ALU。

和超線程一樣黄鳍,既然要調(diào)度一個(gè)不同的任務(wù)過(guò)來(lái)推姻,我們就需要針對(duì)這個(gè)任務(wù),提供更多的執(zhí)行上下文框沟。所以藏古,一個(gè) Core 里面的執(zhí)行上下文的數(shù)量,需要比 ALU 多忍燥。

image

在通過(guò)芯片瘦身拧晕、SIMT 以及超線程技術(shù)此時(shí)的GPU就是一個(gè)更擅長(zhǎng)并行進(jìn)行暴力運(yùn)算的 GPU。

NVidia 2080 顯卡的技術(shù)規(guī)格

2080 一共有 46 個(gè) SM(流式處理器)梅垄,這個(gè) SM 相當(dāng)于 GPU 里面的 GPU Core厂捞,所以你可以認(rèn)為這是一個(gè) 46 核的 GPU,有 46 個(gè)取指令指令譯碼的渲染管線。每個(gè) SM 里面有 64 個(gè) Cuda Core靡馁。你可以認(rèn)為欲鹏,這里的 Cuda Core 就是我們上面說(shuō)的 ALU 的數(shù)量或者 Pixel Shader 的數(shù)量,46x64 呢一共就有 2944 個(gè) Shader臭墨。然后赔嚎,還有 184 個(gè) TMU,TMU也就是用來(lái)做紋理映射的計(jì)算單元胧弛,它也可以認(rèn)為是另一種類型的 Shader尤误。

總結(jié)

GPU 一開始是沒(méi)有“可編程”能力的,程序員們只能夠通過(guò)配置來(lái)設(shè)計(jì)需要用到的圖形渲染效果结缚。隨著“可編程管線”的出現(xiàn)损晤,程序員們可以在頂點(diǎn)處理和片段處理去實(shí)現(xiàn)自己的算法。為了進(jìn)一步去提升 GPU 硬件里面的芯片利用率红竭,微軟在 XBox 360 里面尤勋,第一次引入了“統(tǒng)一著色器架構(gòu)”,使得 GPU 變成了一個(gè)有“通用計(jì)算”能力的架構(gòu)德崭。

接著斥黑,我們從一個(gè) CPU 的硬件電路出發(fā),去掉了對(duì) GPU 沒(méi)有什么用的分支預(yù)測(cè)和亂序執(zhí)行電路眉厨,來(lái)進(jìn)行瘦身锌奴。之后,基于渲染管線里面頂點(diǎn)處理和片段處理就是天然可以并行的了憾股。我們?cè)?GPU 里面可以加上很多個(gè)核鹿蜀。

又因?yàn)槲覀兊匿秩竟芫€里面,整個(gè)指令流程是相同的服球,我們又引入了和 CPU 里的 SIMD 類似的 SIMT 架構(gòu)茴恰。這個(gè)改動(dòng),進(jìn)一步增加了 GPU 里面的 ALU 的數(shù)量斩熊。最后往枣,為了能夠讓 GPU 不要遭遇流水線停頓,我們又在同一個(gè) GPU 的計(jì)算核里面粉渠,加上了更多的執(zhí)行上下文分冈,讓 GPU 始終保持繁忙。

GPU 里面的多核霸株、多 ALU雕沉,加上多 Context,使得它的并行能力極強(qiáng)去件。同樣架構(gòu)的 GPU坡椒,如果光是做數(shù)值計(jì)算的話扰路,算力在同樣價(jià)格的 CPU 的十倍以上。而這個(gè)強(qiáng)大計(jì)算能力倔叼,以及“統(tǒng)一著色器架構(gòu)”汗唱,使得 GPU 非常適合進(jìn)行深度學(xué)習(xí)的計(jì)算模式,也就是海量計(jì)算缀雳,容易并行渡嚣,并且沒(méi)有太多的控制分支邏輯。

使用 GPU 進(jìn)行深度學(xué)習(xí)肥印,往往能夠把深度學(xué)習(xí)算法的訓(xùn)練時(shí)間,縮短一個(gè)绝葡,乃至兩個(gè)數(shù)量級(jí)深碱。而 GPU 現(xiàn)在也越來(lái)越多地用在各種科學(xué)計(jì)算和機(jī)器學(xué)習(xí)上,而不僅僅是用在圖形渲染上了藏畅。

FPGA

由于在設(shè)計(jì)更簡(jiǎn)單一點(diǎn)兒的專用于特定功能的芯片敷硅,少不了要幾個(gè)月。而設(shè)計(jì)一個(gè) CPU愉阎,往往要以“年”來(lái)計(jì)绞蹦。在這個(gè)過(guò)程中,硬件工程師們要設(shè)計(jì)榜旦、驗(yàn)證各種各樣的技術(shù)方案幽七,可能會(huì)遇到各種各樣的 Bug。如果我們每次驗(yàn)證一個(gè)方案溅呢,都要單獨(dú)設(shè)計(jì)生產(chǎn)一塊芯片澡屡,那這個(gè)代價(jià)也太高了。所以后來(lái)就設(shè)計(jì)出FPGA(現(xiàn)場(chǎng)可編程門陣列)這樣一個(gè)芯片咐旧,可以通過(guò)不同的程序代碼驶鹉,來(lái)操作這個(gè)硬件之前的電路連線,不再需要單獨(dú)制造一塊專門的芯片來(lái)驗(yàn)證硬件設(shè)計(jì)铣墨。

它可以像軟件一樣對(duì)硬件編程室埋,可以反復(fù)燒錄,還有海量的門電路伊约,可以組合實(shí)現(xiàn)復(fù)雜的芯片功能姚淆。

FPGA實(shí)現(xiàn)編程式硬件電路三步:

  1. 用存儲(chǔ)換功能實(shí)現(xiàn)組合邏輯。在實(shí)現(xiàn) CPU 的功能的時(shí)候碱妆,我們需要完成各種各樣的電路邏輯肉盹。在 FPGA 里,這些基本的電路邏輯疹尾,不是采用布線連接的方式進(jìn)行的上忍,而是預(yù)先根據(jù)我們?cè)谲浖锩嬖O(shè)計(jì)的邏輯電路骤肛,算出對(duì)應(yīng)的真值表,然后直接存到一個(gè)叫作 LUT(查找表)的電路里面窍蓝。這個(gè) LUT 呢腋颠,其實(shí)就是一塊存儲(chǔ)空間,里面存儲(chǔ)了“特定的輸入信號(hào)下吓笙,對(duì)應(yīng)輸出 0 還是 1”淑玫。
  2. 對(duì)于需要實(shí)現(xiàn)的時(shí)序邏輯電路,我們可以在 FPGA 里面直接放上 D 觸發(fā)器面睛,作為寄存器絮蒿。這個(gè)和 CPU 里的觸發(fā)器沒(méi)有什么本質(zhì)不同。不過(guò)叁鉴,我們會(huì)把很多個(gè) LUT 的電路和寄存器組合在一起土涝,變成一個(gè)叫作邏輯簇的東西。在 FPGA 里幌墓,這樣組合了多個(gè) LUT 和寄存器的設(shè)備但壮,也被叫做 CLB(可配置邏輯塊)。
  3. FPGA 是通過(guò)可編程邏輯布線常侣,來(lái)連接各個(gè)不同的 CLB蜡饵,最終實(shí)現(xiàn)我們想要實(shí)現(xiàn)的芯片功能

我們通過(guò)配置 CLB 實(shí)現(xiàn)的功能有點(diǎn)兒像我們前面講過(guò)的全加器胳施。它已經(jīng)在最基礎(chǔ)的門電路上做了組合溯祸,能夠提供更復(fù)雜一點(diǎn)的功能。更復(fù)雜的芯片功能巾乳,我們不用再?gòu)拈T電路搭起您没,可以通過(guò) CLB 組合搭建出來(lái)。

于是胆绊,通過(guò) LUT 和寄存器氨鹏,我們能夠組合出很多 CLB,而通過(guò)連接不同的 CLB压状,最終有了我們想要的芯片功能仆抵。最關(guān)鍵的是,這個(gè)組合過(guò)程是可以“編程”控制的种冬。而且這個(gè)編程出來(lái)的軟件镣丑,還可以后續(xù)改寫,重新寫入到硬件里娱两。讓同一個(gè)硬件實(shí)現(xiàn)不同的芯片功能莺匠。

ASIC

ASIC:專用集成電路,它是很對(duì)特定的場(chǎng)景來(lái)專門設(shè)計(jì)的芯片十兢,它的電路更精簡(jiǎn)趣竣,單片的制造成本也比 CPU 更低摇庙。而且,因?yàn)殡娐肪?jiǎn)遥缕,所以通常能耗要比用來(lái)做通用計(jì)算的 CPU 更低卫袒。之前說(shuō)的早期的圖形加速卡,其實(shí)就可以看作是一種 ASIC单匣。FPGA 的芯片通過(guò)“編程”的手段也可以編程ASIC夕凝,只是這么做有點(diǎn)兒硬件上了浪費(fèi)。

FPGA相比ASIC的優(yōu)缺點(diǎn):

  • 缺點(diǎn):每一個(gè) LUT 電路户秤,其實(shí)都是一個(gè)小小的“浪費(fèi)”码秉。一個(gè) LUT 電路設(shè)計(jì)出來(lái)之后,既可以實(shí)現(xiàn)與門虎忌,又可以實(shí)現(xiàn)或門泡徙,自然用到的晶體管數(shù)量,比單純連死的與門或者或門的要多得多膜蠢。同時(shí),因?yàn)橛玫木w管多莉兰,它的能耗也比單純連死的電路要大挑围,單片 FPGA 的生產(chǎn)制造的成本也比 ASIC 要高不少
  • 優(yōu)點(diǎn):它沒(méi)有硬件研發(fā)成本糖荒。ASIC 的電路設(shè)計(jì)杉辙,需要仿真、驗(yàn)證捶朵,還需要經(jīng)過(guò)流片蜘矢,變成一個(gè)印刷的電路版,最終變成芯片综看。這整個(gè)從研發(fā)到上市的過(guò)程品腹,最低花費(fèi)也要幾萬(wàn)美元,高的話红碑,會(huì)在幾千萬(wàn)乃至數(shù)億美元舞吭。更何況,整個(gè)設(shè)計(jì)還有失敗的可能析珊。所以羡鸥,如果我們?cè)O(shè)計(jì)的專用芯片,只是要制造幾千片忠寻,那買幾千片現(xiàn)成的 FPGA惧浴,可能遠(yuǎn)比花上幾百萬(wàn)美元,來(lái)設(shè)計(jì)奕剃、制造 ASIC 要經(jīng)濟(jì)得多衷旅。

單個(gè) ASIC 的生產(chǎn)制造成本比 FPGA 低捐腿,ASIC 的能耗也比能實(shí)現(xiàn)同樣功能的 FPGA 要低。能耗低芜茵,意味著長(zhǎng)時(shí)間運(yùn)行這些芯片叙量,所用的電力成本也更低。但是九串,ASIC 有一筆很高的 NRE(一次性工程費(fèi)用)成本绞佩。這個(gè)成本嘴拢,就是 ASIC 實(shí)際“研發(fā)”的成本陕见。只有需要大量生產(chǎn) ASIC 芯片的時(shí)候,我們才能攤薄這份研發(fā)成本街州。

image

虛擬機(jī)

分時(shí)操作系統(tǒng):是使一臺(tái)計(jì)算機(jī)采用時(shí)間片輪轉(zhuǎn)的方式同時(shí)為幾個(gè)烤低、幾十個(gè)甚至幾百個(gè)用戶服務(wù)的一種操作系統(tǒng)肘交。

把計(jì)算機(jī)與許多終端用戶連接起來(lái),分時(shí)操作系統(tǒng)將系統(tǒng)處理機(jī)時(shí)間與內(nèi)存空間按一定的時(shí)間間隔扑馁,輪流地切換給各終端用戶的程序使用涯呻。由于時(shí)間間隔很短,每個(gè)用戶的感覺(jué)就像他獨(dú)占計(jì)算機(jī)一樣腻要。分時(shí)操作系統(tǒng)的特點(diǎn)是可有效增加資源的使用率复罐。例如UNIX系統(tǒng)就采用剝奪式動(dòng)態(tài)優(yōu)先的CPU調(diào)度,有力地支持分時(shí)操作雄家。

虛擬機(jī)技術(shù):就是指在現(xiàn)有硬件的操作系統(tǒng)上效诅,能夠模擬一個(gè)計(jì)算機(jī)系統(tǒng)的技術(shù)。而模擬一個(gè)計(jì)算機(jī)系統(tǒng)趟济,最簡(jiǎn)單的辦法乱投,其實(shí)不能算是虛擬機(jī)技術(shù),而是一個(gè)模擬器顷编。

解釋型虛擬機(jī)

要模擬一個(gè)計(jì)算機(jī)系統(tǒng)戚炫,最簡(jiǎn)單的辦法,就是兼容這個(gè)計(jì)算機(jī)系統(tǒng)的指令集勾效。模擬器是跑在我們的操作系統(tǒng)上的一個(gè)應(yīng)用程序嘹悼。它可以識(shí)別我們想要模擬的、計(jì)算機(jī)系統(tǒng)的程序格式和指令层宫,然后一條條去解釋執(zhí)行杨伙。

這個(gè)方式,其實(shí)和運(yùn)行 Java 程序的 Java 虛擬機(jī)很像萌腿。只不過(guò)限匣,Java 虛擬機(jī)運(yùn)行的是 Java 自己定義發(fā)明的中間代碼(字節(jié)碼),而不是一個(gè)特定的計(jì)算機(jī)系統(tǒng)的指令。字節(jié)碼->機(jī)器碼

這種解釋執(zhí)行方式的最大的優(yōu)勢(shì)就是米死,模擬的系統(tǒng)可以跨硬件锌历。 不過(guò)這個(gè)方式也有兩個(gè)明顯的缺陷。

  1. 第一峦筒,做不到精確的“模擬”究西。很多的老舊的硬件的程序運(yùn)行,要依賴特定的電路乃至電路特有的時(shí)鐘頻率物喷,想要通過(guò)軟件達(dá)到 100% 模擬是很難做到的卤材。
  2. 第二個(gè)缺陷就更麻煩了,那就是這種解釋執(zhí)行的方式峦失,性能實(shí)在太差了扇丛。因?yàn)槲覀儾⒉皇侵苯影阎噶罱唤o CPU 去執(zhí)行的,而是要經(jīng)過(guò)各種解釋和翻譯工作尉辑。

為了解決性能問(wèn)題帆精,也有類似于 Java 當(dāng)中的 JIT 這樣的“編譯優(yōu)化”的辦法,把本來(lái)解釋執(zhí)行的指令隧魄,編譯成 Host 可以直接運(yùn)行的指令卓练。但是,這個(gè)性能還是不能讓人滿意购啄。因?yàn)樗麩o(wú)法保證所有

虛擬機(jī)監(jiān)視器

為了摒棄上述兩個(gè)缺點(diǎn)昆庇,以及需要一個(gè)“全虛擬化”的技術(shù)。也就是說(shuō)闸溃,我們可以在現(xiàn)有的物理服務(wù)器的硬件和操作系統(tǒng)上,去跑一個(gè)完整的拱撵、不需要做任何修改的客戶機(jī)操作系統(tǒng)辉川。這樣的實(shí)現(xiàn)方式就是加一個(gè)中間層,虛擬機(jī)監(jiān)視器拴测。

如果說(shuō)我們宿主機(jī)的 OS 是房東的話乓旗,這個(gè)虛擬機(jī)監(jiān)視器呢,就好像一個(gè)二房東集索。我們運(yùn)行的虛擬機(jī)屿愚,都不是直接和房東打交道,而是要和這個(gè)二房東打交道务荆。我們跑在上面的虛擬機(jī)呢妆距,會(huì)把整個(gè)的硬件特征都映射到虛擬機(jī)環(huán)境里,這包括整個(gè)完整的 CPU 指令集函匕、I/O 操作娱据、中斷等等。

Type-2 型虛擬機(jī)

既然指令都是通過(guò)虛擬機(jī)監(jiān)視器來(lái)執(zhí)行的盅惜,那具體指令是怎么落到硬件上去實(shí)際執(zhí)行的就引申出兩種實(shí)現(xiàn)方式中剩,分別是 Type-1型虛擬機(jī)和Type-2型虛擬機(jī)忌穿。

在 Type-2 虛擬機(jī)里,我們上面說(shuō)的虛擬機(jī)監(jiān)視器好像一個(gè)運(yùn)行在操作系統(tǒng)上的軟件结啼。你的客戶機(jī)的操作系統(tǒng)呢掠剑,把最終到硬件的所有指令,都發(fā)送給虛擬機(jī)監(jiān)視器郊愧。而虛擬機(jī)監(jiān)視器朴译,又會(huì)把這些指令再交給宿主機(jī)的操作系統(tǒng)去執(zhí)行。 - 類似上述模擬器的執(zhí)行模式糕珊,這樣類型的虛擬機(jī)更多是用在我們?nèi)粘5膫€(gè)人電腦里动分,而不是用在數(shù)據(jù)中心里。

Type-1 型虛擬機(jī)

在數(shù)據(jù)中心里面用的虛擬機(jī)红选,我們通常叫作 Type-1 型的虛擬機(jī)澜公。這個(gè)時(shí)候,客戶機(jī)的指令交給虛擬機(jī)監(jiān)視器之后呢喇肋,不再需要通過(guò)宿主機(jī)的操作系統(tǒng)坟乾,才能調(diào)用硬件,而是可以直接由虛擬機(jī)監(jiān)視器去調(diào)用硬件蝶防。 這樣 Type-1 型的虛擬機(jī)也就無(wú)法再在 Intel x86 上面去跑一個(gè) ARM 的程序了甚侣。但帶來(lái)了指令執(zhí)行效率的提升。

所以间学,在 Type-1 型的虛擬機(jī)里殷费,我們的虛擬機(jī)監(jiān)視器其實(shí)并不是一個(gè)操作系統(tǒng)之上的應(yīng)用層程序,而是一個(gè)嵌入在操作系統(tǒng)內(nèi)核里面的一部分低葫。無(wú)論是 KVM详羡、XEN 還是微軟自家的 Hyper-V,其實(shí)都是系統(tǒng)級(jí)的程序嘿悬。

image

因?yàn)樘摂M機(jī)監(jiān)視器需要直接和硬件打交道实柠,所以它也需要包含能夠直接操作硬件的驅(qū)動(dòng)程序。所以 Type-1 的虛擬機(jī)監(jiān)視器更大一些善涨,同時(shí)兼容性也不能像 Type-2 型那么好窒盐。不過(guò),因?yàn)樗话愣际遣渴鹪谖覀兊臄?shù)據(jù)中心里面钢拧,硬件完全是統(tǒng)一可控的蟹漓,這倒不是一個(gè)問(wèn)題了。

Docker

雖然娶靡,Type-1 型的虛擬機(jī)看起來(lái)已經(jīng)沒(méi)有什么硬件損耗牧牢。但是,這里面還是有一個(gè)浪費(fèi)的資源。在我們實(shí)際的物理機(jī)上塔鳍,我們可能同時(shí)運(yùn)行了多個(gè)的虛擬機(jī)伯铣,而這每一個(gè)虛擬機(jī),都運(yùn)行了一個(gè)屬于自己的單獨(dú)的操作系統(tǒng)轮纫。 多運(yùn)行一個(gè)操作系統(tǒng)腔寡,意味著我們要多消耗一些資源在 CPU、內(nèi)存乃至磁盤空間上掌唾。

我們很多時(shí)候想要租用的不是“獨(dú)立服務(wù)器”放前,而是獨(dú)立的計(jì)算資源,或者只要一個(gè)能夠進(jìn)行資源和環(huán)境隔離的“獨(dú)立空間”就好了糯彬。

image

通過(guò) Docker凭语,我們不再需要在操作系統(tǒng)上再跑一個(gè)操作系統(tǒng),而只需要通過(guò)容器編排工具撩扒,比如 Kubernetes 或者 Docker Swarm似扔,能夠進(jìn)行各個(gè)應(yīng)用之間的環(huán)境和資源隔離就好了。

這種隔離資源的方式呢搓谆,也有人稱之為“操作系統(tǒng)級(jí)虛擬機(jī)”炒辉,好和上面的全虛擬化虛擬機(jī)對(duì)應(yīng)起來(lái)。不過(guò)嚴(yán)格來(lái)說(shuō)泉手,Docker 并不能算是一種虛擬機(jī)技術(shù)黔寇,而只能算是一種資源隔離的技術(shù)而已。

總結(jié)

我們現(xiàn)在的云服務(wù)平臺(tái)上斩萌,你能夠租到的服務(wù)器其實(shí)都是虛擬機(jī)缝裤,而不是物理機(jī)。而正是虛擬機(jī)技術(shù)的出現(xiàn)颊郎,使得整個(gè)云服務(wù)生態(tài)得以出現(xiàn)倘是。

虛擬機(jī)是模擬一個(gè)計(jì)算機(jī)系統(tǒng)的技術(shù),而其中最簡(jiǎn)單的辦法叫模擬器袭艺。我們?nèi)粘T?PC 上進(jìn)行 Android 開發(fā),其實(shí)就是在使用這樣的模擬器技術(shù)叨粘。不過(guò)模擬器技術(shù)在性能上實(shí)在不行猾编,所以我們才有了虛擬化這樣的技術(shù)。

在宿主機(jī)的操作系統(tǒng)上升敲,運(yùn)行一個(gè)虛擬機(jī)監(jiān)視器答倡,然后再在虛擬機(jī)監(jiān)視器上運(yùn)行客戶機(jī)的操作系統(tǒng),這就是現(xiàn)代的虛擬化技術(shù)驴党。這里的虛擬化技術(shù)可以分成 Type-1 和 Type-2 這兩種類型瘪撇。

Type-1 類型的虛擬化機(jī),實(shí)際的指令不需要再通過(guò)宿主機(jī)的操作系統(tǒng),而可以直接通過(guò)虛擬機(jī)監(jiān)視器訪問(wèn)硬件倔既,所以性能比 Type-2 要好恕曲。而 Type-2 類型的虛擬機(jī),所有的指令需要經(jīng)歷客戶機(jī)操作系統(tǒng)渤涌、虛擬機(jī)監(jiān)視器佩谣、宿主機(jī)操作系統(tǒng),所以性能上要慢上不少实蓬。不過(guò)因?yàn)榻?jīng)歷了宿主機(jī)操作系統(tǒng)的一次“翻譯”過(guò)程茸俭,它的硬件兼容性往往會(huì)更好一些。

今天安皱,即使是 Type-1 型的虛擬機(jī)技術(shù)调鬓,我們也會(huì)覺(jué)得有一些性能浪費(fèi)。我們常常在同一個(gè)物理機(jī)上酌伊,跑上 8 個(gè)腾窝、10 個(gè)的虛擬機(jī)。而且這些虛擬機(jī)的操作系統(tǒng)腺晾,其實(shí)都是同一個(gè) Linux Kernel 的版本燕锥。于是,輕量級(jí)的 Docker 技術(shù)就進(jìn)入了我們的視野悯蝉。Docker 也被很多人稱之為“操作系統(tǒng)級(jí)”的虛擬機(jī)技術(shù)归形。不過(guò) Docker 并沒(méi)有再單獨(dú)運(yùn)行一個(gè)客戶機(jī)的操作系統(tǒng),而是直接運(yùn)行在宿主機(jī)操作系統(tǒng)的內(nèi)核之上鼻由。所以暇榴,Docker 也是現(xiàn)在流行的微服務(wù)架構(gòu)底層的基礎(chǔ)設(shè)施。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蕉世,一起剝皮案震驚了整個(gè)濱河市蔼紧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狠轻,老刑警劉巖奸例,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異向楼,居然都是意外死亡查吊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門湖蜕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逻卖,“玉大人,你說(shuō)我怎么就攤上這事昭抒∑酪玻” “怎么了炼杖?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)盗迟。 經(jīng)常有香客問(wèn)我坤邪,道長(zhǎng),這世上最難降的妖魔是什么诈乒? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任罩扇,我火速辦了婚禮,結(jié)果婚禮上怕磨,老公的妹妹穿的比我還像新娘喂饥。我一直安慰自己,他們只是感情好肠鲫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布员帮。 她就那樣靜靜地躺著,像睡著了一般导饲。 火紅的嫁衣襯著肌膚如雪捞高。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天渣锦,我揣著相機(jī)與錄音硝岗,去河邊找鬼。 笑死袋毙,一個(gè)胖子當(dāng)著我的面吹牛型檀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播听盖,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼胀溺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了皆看?” 一聲冷哼從身側(cè)響起仓坞,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腰吟,沒(méi)想到半個(gè)月后无埃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毛雇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年录语,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禾乘。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虽缕,靈堂內(nèi)的尸體忽然破棺而出始藕,到底是詐尸還是另有隱情蒲稳,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布伍派,位于F島的核電站江耀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诉植。R本人自食惡果不足惜祥国,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晾腔。 院中可真熱鬧舌稀,春花似錦、人聲如沸灼擂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剔应。三九已至睡腿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間峻贮,已是汗流浹背席怪。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纤控,地道東北人挂捻。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嚼黔,于是被迫代替她去往敵國(guó)和親细层。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345