重點:python
ALU
:
做用:輸出二進制,它會執行計算。算法
兩種內存:
寄存器: 很小的一塊內存,能存一個值。RAM
: 是一大塊內存,能在不一樣地址存大量數字。 (寄存器增大後改形成RAM
)編程
把RAM
, 寄存器
, ALU
放在一塊兒,組件計算機的心臟CPU
(中央處理單元)。緩存
拼個CPU
CPU: 四個寄存器 + 2個寄存器(指令寄存器和指令地址寄存器) + RAM + ALU(指令須要其功能才使用) + 「時鐘」架構
CPU
做用: 負責執行程序。
程序由一個個操做組成,這些操做叫作「指令」,由於它們「指示」計算機要作什麼。若是是數學指令,好比加/減,CPU
會讓ALU
進行數學計算。也多是內存指令,CPU
會和內存通訊,而後讀/寫值。CPU
裏有不少組件。重點放在功能,而不是一根根線具體怎麼連。當用一根線鏈接兩個組件時,這條線只是全部必須線路的一個抽象。這種高層次視角叫「微體系架構」。性能
首先要內存,使用RAM
,爲了保持簡單,假設它只有16個位置,每一個位置存8位。再來四個8位存儲器,叫A, B, C, D。寄存器用來 臨時存數據 和 操做數據。fetch
數據是以二進制存在內存裏。
程序也能夠存在內存裏。優化
能夠給CPU
支持的全部指令,分配一個ID:ui
在這個假設的例子,用前四位存「操做代碼」(operation code),簡稱「操做碼」(opcode),後四位表明數據來自哪裏,能夠是寄存器或內存地址。加密
還須要兩個寄存器,來完成CPU
。
當啓動計算機時,全部寄存器從0開始。
CPU的第一個階段叫「取指令階段」(FETCH PHASE),負責拿到指令。
首先,將「指令地址寄存器」連到RAM
,寄存器的值爲0,所以RAM
返回地址0的值,0010 1110
會複製到「指令寄存器」裏。
如今指令拿到了,要弄清楚是什麼指令,才能執行(execute),而不是殺死(kill)它,這是「解碼階段」。
前4位0010
是LODA A
指令,意思是,把RAM
的值放入寄存器A。
後4位1110
是RAM
的地址,轉成十進制是14。
接下來,指令由「控制單元」進行解碼,就像以前的全部東西,「控制單元」也是邏輯門組成的。
好比:爲了識別「LOAD A」指令,須要一個電路,檢查操做碼是否是0010
,能夠用不多的邏輯門來實現。
指令:
知道了什麼是指令,就能夠開始執行了,開始「執行階段」。
RAM
的「容許讀取線」,把地址14傳過去,RAM
拿到值,0000 0011
,十進制的3。LOAD_A
指令,想把這個值只放到寄存器A,其它寄存器不受影響,因此須要一根線,把RAM
鏈接到4個寄存器,用「檢查是否LOAD_A指令的電路」啓用寄存器A的「容許寫入線」。成功把RAM
地址14的值,是十進制的3,放到了寄存器A:
CPU怎麼執行命令?
「取指令 -> 解碼 -> 執行」
指令完成了,能夠關掉全部線路:
去拿下一條指令,「指令地址寄存器」+1,「執行階段」結束。
LOAD_A
只是CPU能夠執行的各類指令之一,不一樣指令由不一樣邏輯電路解碼,這些邏輯電路會配置CPU內的組件來執行對應操做。
如今計算機支持更多指令,直接操做內存,但原理上是如此。
複雜的邏輯電路解碼,把它封裝成一個總體的「控制單元」。
彙編指令與機器指令就是一一對應。
控制單元就像管絃樂隊的指揮,「指揮」CPU的全部組件。「取指令 -> 解碼 -> 執行」完成。
後續能夠再從「取指令」開始:
RAM
返回地址1裏的值:0001 1111
。0001
是LOAD_B
指令,從RAM
裏把一個值複製到寄存器B,此次內存地址是1111
,十進制的15。RAM
讀地址15,並配置寄存器B接受數據。0001 1110
也就是十進制的14存到了寄存器B。
後續能夠再從「取指令」開始:
RAM
返回地址1裏的值:1000 0100
。RAM
地址,而是2位,2位,分別表明2個寄存器,2位能夠表示4個值(00, 10, 01, 11),因此足夠表示4個寄存器。1000 0100
,表明把寄存器B,加到寄存器A裏。ADD
指令,「控制單元」會,啓用寄存器B,做爲ALU的第一個輸入,還啓用寄存器A,做爲ALU的第二個輸入。ALU能夠執行不一樣操做,因此控制單元必須傳遞ADD操做碼告知ALU要作什麼。0001 0001
。ADD
結束。
後續能夠再從「取指令」開始:
RAM
返回地址3裏的值:0100 1101
。STORE A
指令(把寄存器A的值放入到內存)RAM
地址13中。RAM
,但此次不是「容許讀取」而是「容許寫入」;同時,打開寄存器A的「容許讀取」,這樣就能夠把寄存器A裏的值,傳給RAM
。
運行第一個電腦程序:
它從內存中加載兩個值,相加,而後把它結果放回到內存中。
人工切換CPU的狀態:「取指令 -> 解碼 -> 執行」。
最後:STORE_A 13
指令執行完以後,再執行HALT
指令。不然CPU會不停運行下去,處理後面的0。由於0不是操做碼,因此電腦會崩掉。
通常來講RAM
沒寫的話,裏面會儲存亂碼而不是0(不HALT
就會崩掉)。
時鐘
是「時鐘」來負責管理CPU的節奏,時鐘以精確的間隔,觸發電信號,「控制單元」會用這個信號,推動CPU的內部操做。就像羅馬帆船的船頭,有一我的負責按節奏的擊鼓,讓全部划船的人同步。就像節拍器同樣。
節奏不能太快,由於就算是電也要必定時間來傳輸,CPU「取指令 -> 解碼 -> 執行」的速度叫「時鐘速度」。單位是赫茲,赫茲是用來表示頻率的單位。1赫茲表明一秒1個週期。
前面的步驟:讀取 -> 讀取 -> 相加 -> 存儲
時鐘速度大概是0.03赫茲,(1/360 = 0.03Hz)。
第一個單芯片CPU是「英特爾 4004」1971年發佈的4位CPU。
它的微架構:
雖然是第一個單芯片的處理器,但它的時鐘速度達到了740千赫茲 - 每秒74萬次。
一兆赫茲是1秒1百萬個時鐘週期,如今的電腦或手機,確定有幾千兆赫茲。1秒10億次時鐘週期。
計算機超頻,意思是修改時鐘速度,加快CPU的速度。芯片製造商常常會給CPU留一點餘地,能夠接受一點超頻。但超頻太多會讓CPU過熱,或產生亂碼,由於信號跟不上時鐘(超頻通常不會讓計算機起火)。
降頻,有時候沒有必要讓處理器全速運行,可能用戶走開了,或者在跑一個性能要求較低的程序,把CPU的速度降下來,能夠省不少電。省電對用戶電池的設備很重要,好比筆記本和手機。
爲了儘量省電,不少現代處理器能夠按需求,加快或減慢時鐘速度。這叫「動態調整頻率」。加上時鐘後,CPU纔是完整的。
把寄存器
和ALU
和時鐘
封裝在一塊兒:
RAM
,是在CPU
外邊的獨立組件。CPU
和RAM
之間用「地址線」,「數據線」和「容許讀/寫線」進行通訊。
重點:運行一遍程序。
LOAD_A
, LOAD_B
, SUB
, JUMP
, ADD
, HALT
等指令。JUMP NEGATIVE
是負數才跳轉,還有其餘類型的JUMP
。把ALU
, 控制單元, RAM
, 時鐘結合在一塊兒,作了一個基本,但可用的「中央處理單元」,簡稱CPU。它是計算機的核心。CPU之因此強大,是由於它是可編程的,若是寫入不一樣指令,就會執行不一樣任務。CPU是一塊硬件,能夠被軟件控制。
介紹指令集
LOAD_A
寄存器ALOAD_B
寄存器BADD
相加。STORE_A
存儲寄存器A,並寫入RAM
中。SUB
減法,和ADD
同樣也要2個寄存器來操做。JUMP(跳轉)
,讓程序跳轉到新位置。若是想改變指令順序,或跳過一些指令,這個就很實用。例如:JUMP 0
,能夠調回開頭。JUMP
在底層的實現方式是:把指令後4位表明的內存地址的值覆蓋掉「指令地址寄存器」裏的值。 JUMP_NEGATIVE
,它只在ALU的「負數標誌」爲真時,進行JUMP
。算數結果爲負,「負數標誌」纔是真,結果不是負數時,「負數標誌」爲假,若是是假,JUMP_NEGATIVE
就不會執行程序照常運行。HALT(中止)
,計算機還須要知道何時該停下來。條件跳轉
指令和數據都是存在同一個內存裏的。它們在根本層面上毫無區別,都是二進制數。 HALT
很重要,能區分指令和數據。
JUMP
讓程序更完整一些:
如今從CPU的視角走一遍程序。
LOAD_A 14
,把1存入寄存器A(地址14裏值是1)。LOAD_B 15
,把1存入寄存器B(地址15裏值是1)。ADD B A
把寄存器B和A相加,結果放到寄存器A裏。如今寄存器A的值是2,(以二進制存儲的)STORE_A 13
指令,把寄存器A的值存入內存地址13。JUMP 2
指令,CPU會把「指令寄存器」的值,如今的「4」,改爲「2」。所以下一步不是HALT
。ADD B A
,寄存器A裏的2,寄存器B裏的1。1+2=3,寄存器A變成3。存入內存。又碰到JUMP 2
,回到ADD B A
,1+3=4。每次循環都+1
,不斷增多。若是按照這樣執行下去,永遠不會碰到HALT
,老是會碰到JUMP 2
。這個叫無限循環(INFINITE LOOP)∞。
JUMP 2
指令:
爲了中止下來,須要有條件的JUMP
,只有特定條件知足了,才執行JUMP
。
好比:JUMP NEGATIVE
就是條件跳轉的一個例子。
還有其它類型的條件跳轉,好比,JUMP IF EQUAL(若是相等)
,JUMP IF GREATER(若是更大)
。
for
循環是有個數限制,while和python裏的repeat是無限循環。
執行下圖程序:
SUB B A
,用A減B,11-5=6。如今寄存器A的值是6JUMP_NEG 5
,ALU上次計算的是6,是正數,因此「負數標誌」是假。所以處理器不會執行JUMP
,繼續下一條指令。JUMP 2
,JUMP 2
沒有條件,直接執行SUB B A
, 6-5=1。如今寄存器A的值是1。JUMP_NEG 5
,由於1仍是正數,所以JUMP NEGATIVE
不會執行,來到下一條指令。JUMP 2
,直接執行SUB B A
, 1-5=-4。如今寄存器A的值是-4。此次ALU
的「負數標誌」是真。如今寄存器A的值是-4。JUMP_NEG 5
, CPU的執行跳轉到內存地址5的指令ADD B A
。跳出了無限循環。ADD B A
,-4+5=1,存入寄存器A,如今寄存器A的值是1。STORE_A 13
,存入內存地址13HALT
指令,中止程序。
指令順序:
LOAD_A 14
值是11LOAD_B 15
值是5SUB B A
寄存器A值6(11-5)JUMP_NEG 5
"負數標誌"falseJUMP 2
跳轉會地址2再次依次執行SUB B A
寄存器A值1(6-5)JUMP_NEG 5
"負數標誌"falseJUMP 2
跳轉會地址2再次依次執行SUB B A
寄存器A值-4(1-5)JUMP_NEG 5
"負數標誌"trueADD B A
寄存器A值1(-4+5)STORE_A 13
存入內存地址13HALT
指令,程序結束這段程序只有7個指令,但CPU執行了13個指令。由於在內部循環了2次。
這段代碼是算餘數,11除5餘1 -> 11-5-5-5+5 = 1
軟件能夠作到硬件作不到的事。ALU
可沒有除法功能,是程序給了除法這個功能。
假設的CPU很基礎,全部指令都是8位,操做碼只佔了前4位,即使用盡4位,也只能表明16個指令。並且有幾條指令,是用後4位來指定內存地址。只能表示16個指令,並很少,甚至不用使用JUMP 17
,由於4位二進制沒法表示數字17,所以,真正的現代CPU用兩種策略。
HALT
指令,HALT
不須要額外數據,那麼會立刻執行。若是看到JUMP
,它得知道位置值,這個值在JUMP
的後邊。這個叫「當即值」,這樣設計,指令能夠是任意長度。但會讓讀取階段複雜一些。某些指令不須要操做內存因此能夠省下內存那四位。
可是JUMP
也須要4位操做碼,這樣仍是隻有4位來表示內存地址。
通常來講 (指令=操做碼+操做值地址)。當(指令=操做碼+操做值)時,這個操做值就是當即值。
這樣大於8位的地址,能夠經過屢次重複讀取的方式得到,這也是讀取階段相對麻煩的地方。
當即值是指令+位置
英特爾4004處理器
1971年,英特爾發佈了4004處理器。這是第一次把CPU作成一個芯片,給後來的英特爾處理器打下基礎。
JUMP
, ADD
, SUB
, LOAD
JUMP
,以表示更多內存地址。處理器從1971年到如今發展巨大,現代CPU,好比英特爾酷睿i7,有上千個指令和指令變種。長度從1到15個字節。
例如:光ADD
指令就不少變種。
指令愈來愈多,是由於給CPU設計了愈來愈多功能。
從1秒1次運算,到如今千兆赫甚至兆赫的CPU。如今看視頻的設備也有GHz
速度。
1秒10億條指令,這是很大的計算量。
減小晶體管切換時間
早期計算機的提速方式是,減小晶體管的切換時間。
晶體管組成了邏輯門,ALU
以及其它計算機組件。
這種提速方法最終會碰到瓶頸,因此處理器廠商,發明了各類新技術來提高性能,不但讓簡單指令運行更快,也讓它能進行更復雜的運算。
除法的程序,給CPU執行,方法是作一連串減法。好比16除4會變成。16-4-4-4-4
,碰到0或負數才停下來。這種方法要多個時鐘週期,很低效。因此現代CPU直接在硬件層面設計了除法,能夠直接給ALU除法指令。
除法電路
擁有除法的ALU更大也更復雜一些。但也更厲害,複雜度vs速度 的平衡在計算機發展史上常常出現。
例如:現代處理器有專門電路來處理圖形操做,解碼壓縮視頻,加密文檔等等。若是用標準操做來實現,要不少個時鐘週期。可能據說過處理器MMX
, 3DNOW
, SEE
。它們有額外電路作更復雜的操做。用於遊戲和加密等場景。
指令不斷增長,一旦習慣了它的便利就很難刪掉,因此爲了兼容舊指令集,指令數量愈來愈多。
英特爾4004,第一個集成CPU,有46條指令,足夠作一臺能用的計算機。但現代處理器有上千條指令,有各類巧妙複雜的電路。
超高的時鐘速度帶來另一個問題: 如何快速傳遞數據給CPU。就像有強大的蒸汽機,但沒法快速加煤。RAM
成了瓶頸。
RAM
是CPU以外的獨立組件,意味着數據要用線來傳遞,叫「總線」。
總線可能只有幾釐米,別忘了電信號的傳輸接近光速。但CPU每秒能夠處理上億條指令。很小的延遲也會形成問題。
給CPU加緩存
RAM
還須要時間找地址,取數據,配置,輸出數據。一條「從內存讀數據」的指令可能要多個時鐘週期,CPU空等數據。解決延遲的方法之一是:給CPU加一點RAM,叫「緩存」。
由於處理器裏空間不大,因此緩存通常只有KB
或MB
,而RAM
都是GB
起步。
緩存提升了速度,CPU從RAM
拿數據時,RAM
不用傳一個,能夠傳一批。雖然花的時間久一點,但數據能夠存在緩存。
這很實用,由於數據經常是一個個按順序處理。
例如:算餐廳的當日收入。先取RAM
地址100的交易額,RAM
與其只給1個值,直接給一批值。把地址100到200都複製到緩存,當處理器要下一個交易額時,地址101,緩存會說:「我有下一個交易額的值」,而不用向RAM
取數據。
由於緩存離CPU近,一個時鐘週期就能給數據,CPU不用空等。
比反覆去RAM
拿數據快得多,若是想要的數據已經在緩存,叫「緩存命中」。若是想要的數據不在緩存,叫「緩存未命中」。
緩存也能夠當臨時空間,存一些中間值,適合長/複雜的運算。
若是計算完餐廳一天銷售額,想把結果存到地址150,就像以前,數據不是直接存到RAM
,而是存在緩存,這樣不但存起來快一些,若是還要接着算,取值也快一些。
但這樣帶來一個問題,緩存和RAM
不一致了,這種不一致必須記錄下來,以後要同步。所以緩存裏每塊空間,有一個特殊標記,叫「髒位(Dirty bit)」。
同步通常發生在,當緩存滿了而CPU又要緩存時,在清理緩存騰出空間以前,會先檢查「髒位」,若是是「髒」的,在加載新內容以前,會把數據寫會到RAM
。
流水線設計
提高性能方法叫「指令流水線」。
例如:洗一整個酒店的牀單,但只有1個洗衣機, 1個乾燥機。
選擇1: 按順序來,放洗衣機等30分鐘洗完,而後拿出溼牀單,放進乾燥機等30分鐘烘乾。這樣1小時洗一批。
須要用「並行處理」進一步提升效率,先放一批牀單到洗衣機,等30分鐘洗完,而後溼牀單放進乾燥機,但此次,與其乾等30分鐘烘乾,能夠放另外一批進洗衣機。讓兩臺機器同時工做,30分鐘後,一批牀單完成,另外一批完成一半,另外一批准備開始,效率是翻倍的。
處理器也能夠這樣子設計,CPU是按序處理,取指令(fetch) -> 解碼(decode) -> 執行(execute)
,不斷重複。這種設計,三個時鐘週期執行1條指令。
並行處理
但由於每一個階段用的是CPU的不一樣部分,意味着能夠並行處理,「執行」一個指令時,同時能夠「解碼」下一個指令,「讀取」下下個指令。不一樣任務重疊進行,同時用上CPU裏全部部分。這樣的流水線,每一個時鐘週期執行1個指令,吞吐量*3。
和緩存同樣,這也會帶來一些問題:
JUMP
指令會停一下子等待條件值肯定下來,一旦JUMP
的結果出來了,處理器就繼續流水線。但由於空等會形成延遲,因此高端處理器會用一些技巧(調度算法,冒險分支)。JUMP
想象成是「岔路口」,高端CPU會猜哪條路的可能性大一些,而後提早把指令放進流水線,這叫「推測執行(speculative execution)」。當JUMP
的結果出了,若是CPU
猜對了,流水線已經塞滿正確能夠執行的指令,能夠立刻運行。若是CPU
猜錯了,就要清空流水線,就像走錯路掉頭。多個ALU
理想狀況下,流水線一個時鐘週期完成1個指令,而後「超標量處理器」出現了,一個時鐘週期完成多個指令。即使有「流水線設計」,在指令執行階段,處理器裏有些區域仍是可能會空閒,好比,執行一個「從內存取值」指令期間ALU
會閒置。因此一次性處理多條指令(取指令+解碼)會更好。
能夠再進一步,加多幾個相同的電路,執行出現頻次很高的指令。(一核有難,多核圍觀)
例如:不少CPU有四個,八個甚至更多徹底相同的ALU
。能夠同時執行多個數學運算。
目前說過的方法:「緩存」,「指令流水線」,「多個ALU」,都是優化1個指令流的吞吐量。
多核(Code)
另一個提高性能的方法是:同時運行多個指令流,用多核處理器。(七核看戲)
雙核或四核處理器,意思是一個CPU芯片裏,有多個獨立處理單元。很像是有多個獨立CPU,但由於它們整合緊密,能夠共享一些資源。好比緩存,使得多核能夠合做運算。但多核不夠時,能夠用多個CPU。
高端計算機,視頻觀看的計算機,須要更多的馬力,讓上百人以上可以同時流暢的觀看,2個或4個CPU是最多見的,但有時有更高的性能需求,因此造了超級計算機。
好比模擬宇宙的造成,須要強大的計算能力。給普通的臺式機加幾個CPU沒什麼用,須要更多的處理器。目前爲止,最快的計算機在中國無錫的「神威 太湖之光」,有40960個CPU,每一個CPU有256和核心,總共超過1千萬個核心,每一個核心的頻率是1.45GHz,每秒能夠進行9.3億億次浮點運算。
現代的處理器不但大大的提升了速度,並且也變得更復雜,用各類技巧,榨乾每一個時鐘週期,作儘量多的運算。