上一講裏呢,我進一步爲你講解了CPU裏的「黑科技」,分別是超標量(Superscalar)技術和超長指令字(VLIW)技術。python
超標量(Superscalar)技術可以讓取指令以及指令譯碼也並行進行;在編譯的過程,超長指令字(VLIW)技術能夠搞定指令前後的依賴關係,使得一次能夠取一個指令包。算法
不過,CPU裏的各類神奇的優化咱們還遠遠沒有說完。這一講裏,我就帶你一塊兒來看看,專欄裏最後兩個提高CPU性能的架構設計。它們分別是,你應該經常據說過的 超線程(Hyper-Threading)技術,以及可能沒
有那麼熟悉的 單指令多數據流(SIMD)技術。數據庫
不知道你是否是還記得,在第21講,我給你介紹了Intel是怎麼在Pentium 4處理器上遭遇重大失敗的。若是不太記得的話,你能夠回過頭去回顧一下。數組
那時我和你說過,Pentium 4失敗的一個重要緣由,就是它的CPU的流水線級數太深了。早期的Pentium 4的流水線深度高達20級,然後期的代號爲Prescott的Pentium 4的流水線級數,更是到了31級。超長的流水
線,使得以前咱們講的不少解決「冒險」、提高併發的方案都用不上。瀏覽器
由於這些解決「冒險」、提高併發的方案,本質上都是一種 指令級並行(Instruction-level parallelism,簡稱IPL)的技術方案。換句話說就是,CPU想要在同一個時間,去並行地執行兩條指令。而這兩條指令呢,
本來在咱們的代碼裏,是有前後順序的。不管是咱們在流水線裏面講到的流水線架構、分支預測以及亂序執行,仍是咱們在上一講說的超標量和超長指令字,都是想要經過同一時間執行兩條指令,來提高CPU的吞吐率bash
然而在Pentium 4這個CPU上,這些方法均可能由於流水線太深,而起不到效果。我以前講過,更深的流水線意味着同時在流水線裏面的指令就多,相互的依賴關係就多。因而,不少時候咱們不得不把流水線停頓下
來,插入不少NOP操做,來解決這些依賴帶來的「冒險」問題。多線程
不知道是否是由於當時面臨的競爭太激烈了,爲了讓Pentium 4的CPU在性能上更有競爭力一點,2002年末,Intel在的3.06GHz主頻的Pentium 4 CPU上,第一次引入了 超線程(Hyper-Threading)技術。架構
什麼是超線程技術呢?Intel想,既然CPU同時運行那些在代碼層面有先後依賴關係的指令,會遇到各類冒險問題,咱們不如去找一些和這些指令徹底獨立,沒有依賴關係的指令來運行好了。那麼,這樣的指令哪裏來
呢?天然同時運行在另一個程序裏了。併發
你所用的計算機,其實同一個時間能夠運行不少個程序。好比,我如今一邊在瀏覽器裏寫這篇文章,後臺一樣運行着一個Python腳本程序。而這兩個程序,是徹底相互獨立的。它們兩個的指令徹底並行運行,而不
會產生依賴問題帶來的「冒險」。機器學習
然而這個時候,你可能就會以爲奇怪了,這麼作彷佛不須要什麼新技術呀。如今咱們用的CPU都是多核的,原本就能夠用多個不一樣的CPU核心,去運行不一樣的任務。即便當時的Pentium 4是單核的,咱們的計算機本
來也能同時運行多個進程,或者多個線程。這個超線程技術有什麼特別的用處呢?
不管是上面說的多個CPU核心運行不一樣的程序,仍是在單個CPU核內心面切換運行不一樣線程的任務,在同一時間點上,一個物理的CPU核心只會運行一個線程的指令,因此其實咱們並無真正地作到指令的並行運行。
超線程可不是這樣。超線程的CPU,實際上是把一個物理層面CPU核心,「假裝」成兩個邏輯層面的CPU核心。這個CPU,會在硬件層面增長不少電路,使得咱們能夠在一個CPU核心內部,維護兩個不一樣線程的指令的狀態信息。
好比,在一個物理CPU核心內部,會有雙份的PC寄存器、指令寄存器乃至條件碼寄存器。這樣,這個CPU核心就能夠維護兩條並行的指令的狀態。在外面看起來,彷佛有兩個邏輯層面的CPU在同時運行。因此,超線
程技術通常也被叫做 同時多線程(Simultaneous Multi-Threading,簡稱SMT)技術 。
不過,在CPU的其餘功能組件上,Intel可不會提供雙份。不管是指令譯碼器仍是ALU,一個CPU核心仍然只有一份。由於超線程並非真的去同時運行兩個指令,那就真的變成物理多核了。超線程的目的,是在一個
線程A的指令,在流水線裏停頓的時候,讓另一個線程去執行指令。由於這個時候,CPU的譯碼器和ALU就空出來了,那麼另一個線程B,就能夠拿來幹本身須要的事情。這個線程B可沒有對於線程A裏面指令的
關聯和依賴。
這樣,CPU經過很小的代價,就能實現「同時」運行多個線程的效果。一般咱們只要在CPU核心的添加10%左右的邏輯功能,增長能夠忽略不計的晶體管數量,就能作到這一點。
不過,你也看到了,咱們並無增長真的功能單元。因此超線程只在特定的應用場景下效果比較好。通常是在那些各個線程「等待」時間比較長的應用場景下。好比,咱們須要應對不少請求的數據庫應用,就很適合
使用超線程。各個指令都要等待訪問內存數據,可是並不須要作太多計算。
因而,咱們就能夠利用好超線程。咱們的CPU計算並無跑滿,可是每每當前的指令要停頓在流水線上,等待內存裏面的數據返回。這個時候,讓CPU裏的各個功能單元,去處理另一個數據庫鏈接的查詢請求就是
一個很好的應用案例。
我這裏放了一張個人電腦裏運行CPU-Z的截圖。你能夠看到,在右下角里,個人CPU的Cores,被標明瞭是4,而Threads,則是8。這說明我手頭的這個CPU,只有4個物理的CPU核心,也就是所謂的4核CPU。可是
在邏輯層面,它「裝做」有8個CPU核心,能夠利用超線程技術,來同時運行8條指令。若是你用的是Windows,能夠去下載安裝一個CPU-Z來看看你手頭的CPU裏面對應的參數。
在上面的CPU信息的圖裏面,你會看到,中間有一組信息叫做Instructions,裏面寫了有MMX、SSE等等。這些信息就是這個CPU所支持的指令集。這裏的MMX和SSE的指令集,也就引出了我要給你講的最後一個提
升CPU性能的技術方案, SIMD,中文叫做 單指令多數據流(Single Instruction Multiple Data)。
咱們先來體會一下SIMD的性能到底怎麼樣。下面是兩段示例程序,一段呢,是經過循環的方式,給一個list裏面的每個數加1。另外一段呢,是實現相同的功能,可是直接調用NumPy這個庫的add方法。在統計兩段
程序的性能的時候,我直接調用了Python裏面的timeit的庫。
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 >>>
從兩段程序的輸出結果來看,你會發現,兩個功能相同的代碼性能有着巨大的差別,足足差出了30多倍。也難怪全部用Python講解數據科學的教程裏,每每在一開始就告訴你不要使用循環,而要把全部的計算都向量化(Vectorize)。
有些同窗可能會猜想,是否是由於Python是一門解釋性的語言,因此這個性能差別會那麼大。第一段程序的循環的每一次操做都須要Python解釋器來執行,而第二段的函數調用是一次調用編譯好的原生代碼,所
以纔會那麼快。若是你這麼想,不妨試試直接用C語言實現一下1000個元素的數組裏面的每一個數加1。你會發現,即便是C語言編譯出來的代碼,仍是遠遠低於NumPy。緣由就是,NumPy直接用到了SIMD指令,可以並行進行向量的操做。
而前面使用循環來一步一步計算的算法呢,通常被稱爲 SISD,也就是 單指令單數據(Single InstructionSingle Data)的處理方式。若是你手頭的是一個多核CPU呢,那麼它同時處理多個指令的方式能夠叫
做 MIMD,也就是 多指令多數據(Multiple Instruction Multiple Dataa)。
爲何SIMD指令能快那麼多呢?這是由於,SIMD在獲取數據和執行指令的時候,都作到了並行。一方面,在從內存裏面讀取數據的時候,SIMD是一次性讀取多個數據。
就以咱們上面的程序爲例,數組裏面的每一項都是一個integer,也就是須要 4 Bytes的內存空間。Intel在引入SSE指令集的時候,在CPU裏面添上了8個 128 Bits的寄存器。128 Bits也就是 16 Bytes ,也就是說,一個
寄存器一次性能夠加載 4 個整數。比起循環分別讀取4次對應的數據,時間就省下來了。
在數據讀取到了以後,在指令的執行層面,SIMD也是能夠並行進行的。4個整數各自加1,互相以前徹底沒有依賴,也就沒有冒險問題須要處理。只要CPU裏有足夠多的功能單元,可以同時進行這些計算,這個加法
就是4路同時並行的,天然也省下了時間。
因此,對於那些在計算層面存在大量「數據並行」(Data Parallelism)的計算中,使用SIMD是一個很划算的辦法。在這個大量的「數據並行」,其實一般就是實踐當中的向量運算或者矩陣運算。在實際的程序開發
過程當中,過去一般是在進行圖片、視頻、音頻的處理。最近幾年則一般是在進行各類機器學習算法的計算。而基於SIMD的向量計算指令,也正是在Intel發佈Pentium處理器的時候,被引入的指令集。當時的指令集
叫做 MMX,也就是Matrix Math eXtensions的縮寫,中文名字就是 矩陣數學擴展。而Pentium處理器,也是CPU第一次有能力進行多媒體處理。這也正是拜SIMD和MMX所賜。
從Pentium時代開始,咱們能在電腦上聽MP三、看VCD了,而不用專門去買一塊「聲霸卡」或者「顯霸卡」了。沒錯,在那以前,在電腦上看VCD,是須要專門買可以解碼VCD的硬件插到電腦上去的。而到了今
天,經過GPU快速發展起來的深度學習技術,也同樣受益於SIMD這樣的指令級並行方案,在後面講解GPU的時候,咱們還會遇到它。
這一講,咱們講完了超線程和SIMD這兩個CPU的「並行計算」方案。超線程,實際上是一個「線程級並行」的解決方案。它經過讓一個物理CPU核心,「裝做」兩個邏輯層面的CPU核心,使得CPU能夠同時運行
兩個不一樣線程的指令。雖然,這樣的運行仍然有着種種的限制,不少場景下超線程並不必定能帶來CPU的性能提高。可是Intel經過超線程,讓使用者有了「佔到便宜」的感受。一樣的4核心的CPU,在有些狀況下能
夠發揮出8核心CPU的做用。而超線程在今天,也已經成爲Intel CPU的標配了。
而SIMD技術,則是一種「指令級並行」的加速方案,或者咱們能夠說,它是一種「數據並行」的加速方案。在處理向量計算的狀況下,同一個向量的不一樣維度之間的計算是相互獨立的。而咱們的CPU裏的寄存
器,又能放得下多條數據。因而,咱們能夠一次性取出多條數據,交給CPU並行計算。
正是SIMD技術的出現,使得咱們在Pentium時代的我的PC,開始有了多媒體運算的能力。能夠說,Intel的MMX、SSE指令集,和微軟的Windows 95這樣的圖形界面操做系統,推進了PC快速進入家庭的歷史進程。