當前位置:
首頁 > 最新 > 從x86流水線層面,談談如何進行性能優化

從x86流水線層面,談談如何進行性能優化

前言

性能優化,關鍵在於伺候好 CPU。作為一個追求性能極致的程序員,了解 CPU的內部機制是一個不可迴避的話題。這是一個需要日積月累的持續的過程,但也並不需要深入到數字電路的程度,就像一個設計 CPU的專家並不一定精通軟體設計一樣,你也並不需要成為一個 CPU專家才能寫出高性能的軟體。

作為一小撮人類精英送給普羅大眾的珍貴禮物,能在市場上隨意購買到的 CPU其實和買不到的核武器一樣代表了人類最尖端的科技水平。即便是一位 x86 CPU專家也只能無一遺漏地講清楚他所專攻的那一部分內容。對於我們來說,雖然不可能盡懂,但有三個部分的內容十分關鍵:流水線、緩存和指令集。這三個部分之中,「流水線」可以作為一條貫穿的線索。因此,承接上一篇文章中的示例,我們先來了解一下流水線。

基本概念

CPU的主要工作是依據指令執行對數據的操作。這句話基本上解釋了什麼是流水線。我知道能點開這篇文章的人都不可能對「流水線」這個概念一無所知,我也不想一上來就鋪陳大段大段教科書式的文本,羅列各個概念的定義,這完全是在一心一意地捨本逐末。技術的發展只是事物矛盾的一種運動形式,這次我們將嘗試從 CPU的歷史沿革的角度切入對流水線各個組件的介紹。

從 40年前 Intel生產第一顆 8086處理器直到今天,CPU的變化已經讓你覺得以前的處理器都只能叫做「單片機」。但即便真的是淘寶上幾毛錢一個的單片機,也有和今天的 i7處理器相通的地方。8086處理器有 14個今天仍在使用的寄存器:4個通用寄存器 (General Purpose Register),4個段寄存器 (Segment Register),4個索引寄存器 (Index Register),1個標誌位寄存器 (EFLAGS Register)用於標示 CPU狀態,以及最後一個,指令指針寄存器 (Instruction Pointer Register),用來保存下一個需要執行的指令的地址。這個指令指針寄存器,就直接涉及到流水線的操作過程,它的持續存在,也表明了流水線基本原理的時間一致性。

從 40年前到現在,所有 CPU執行過的指令都遵循以下的流程:CPU首先依據指令指針取得 (Fetch)將要執行的指令在代碼段的地址,接下來解碼 (Decode)地址上的指令。解碼之後,會進入真正的執行 (Execute)階段,之後會是「寫回」(Write Back)階段,將處理的最終結果寫回內存或寄存器中,並更新指令指針寄存器指向下一條指令。這基本上是一個完全符合人類邏輯的設計方案。

最初,也是最自然地,CPU會一個接一個地處理全部指令。每一個指令都按上面的過程執行完畢,然後執行下一個指令。那個時候的主要矛盾還是軟體日益增長的性能需求同落後的 CPU處理速度之間的矛盾。在摩爾定律的正確指導下,CPU建設工作取得了歷史性成果,主要矛盾發生了轉移:CPU的執行速度慢慢快過了內存讀寫的速度。所以每次都去內存讀取指令越來越成為不能承受之重,因此在 1982年,處理器中引入了指令緩存。

當 CPU的速度越來越快,數據緩存作為矛盾雙方互相妥協的產物也引入到處理器之中。但這些都不是治本之法。矛盾的主要方面在於,CPU並沒有以飽和的狀態運轉。於是在 1989年,i486處理器建設性地引入了五級流水線。其思路就是以拉動內需的方式消化 CPU的過剩產能:改一次只能處理一條指令為一次處理五條。

從網上以「CPU pipeline」為關鍵字搜索總會找到類似下圖的圖片:

我不知道諸位怎麼看,反正我對著這幅圖理解起來總是有困難。提供一個簡單的理解:將每條指令都想像為一個待加工的產品,在一條有 5個加工工序的流水線上魚貫而入。這樣可以讓 CPU的每一道工序始終保持工作量飽和,也就從根本上提升了指令的吞吐和程序的性能。

流水線引入的問題

考慮一個簡單的交換變數值的代碼:

1. `a = a ^ b;` 2. `b = a ^ b;` 3. `a = b ^ a;`指令,按上圖 i486流水線的示意,第一條指令進入流水線 Fetch階段,然後進入 D1階段,此時第二條指令進入 Fetch。在下一個機器周期,第一條指令進入 D2,第二條進入 D1,同時 Fetch第三條指令。到此為止一切正常,但下一個機器周期,當第一條指令進入 Execute階段的時候,第二條指令並不能繼續進入下一階段,因為它所需要的變數的最終結果,必須在第一條指令執行完畢之後才能獲得。所以第二條指令會阻塞在流水線之上,等第一條指令執行完畢才會繼續。而在第二條指令執行的過程中,第三條指令也會有類似的遭遇。當出現了流水線阻塞的情況,指令的流水線式執行就會與單獨執行之間拉開距離,這被稱為流水線「氣泡」(bubble)。

時鐘周期:也叫震蕩周期。是時鐘頻率(主頻)的倒數,是最小的時間周期

機器周期:流水線中的每個階段稱為一個基本操作,完成一個基本操作所需要的時間為機器周期

指令周期:執行一條指令所需要的時間,一般由多個機器周期組成

除了上面的情況,還有一種常見的原因導致氣泡的產生。執行每條指令所需要消耗的時間(指令周期)是不同的。當一條簡單指令前面是一條耗時較長的複雜指令的時候,簡單指令不得不等待覆雜指令。另外,如果程序里出現這類分支呢?這些情況都會導致流水線不能滿負荷工作,從而導致性能的相對下降。

在面對問題的時候,人總是會傾向於引入一個更複雜的機制來解決問題,多級流水線就是一個例子。複雜可以反映出技術的改良,但「複雜」本身就是一個新的問題。這也許就是矛盾永遠不會消失,技術也不會停止進步的原因。但「為學日益,為道日損」,愈發複雜的機制總會在某個時機之下發生大破大立,但可能現在時機還沒有到來。面對「氣泡」問題,處理器又引入了一個更複雜的解決方案——1995年 Intel發布 Pentium Pro處理器時,加入了亂序執行核心 (Out-of-order core, OOO core)。

亂序執行核心(OOO core)

其實亂序執行的思想很簡單:當下一條指令被阻塞的時候,從後面的指令里再找一條能執行的就好了嘛。但要完成這個工作卻相當複雜。首先要保證程序的最終結果與順序執行一致,同時要識別各類數據依賴。要達到理想的效果,除了並行執行之外,還需要對指令的粒度進一步細化,以達到以無厚入有間的效果,這樣就引入了「微操作」(micro-operations, μ-ops)的概念。在流水線的 Decode階段,彙編指令又被進一步拆解,最終的產物就是一系列的微操作。

上圖就是引入亂序處理核心之後的指令μ-ops處理流程。不同顏色的模塊對應第一張圖中不同顏色的流水線處理階段。

Fetch階段沒有太多變化,在 Decode階段,可以並行對四條指令解碼,解碼的最終產物就是上面提到的μ-ops。後面的 Register Alias Table和 Reorder Buffer可以當做是亂序執行核心的預處理階段。

對於並行執行的微操作,或者亂序執行的操作,很有可能會同時讀寫同一個寄存器。所以在處理器內部,原始的寄存器便被「別名」(aliased)為內部對軟體工程師不可見的寄存器,這樣原本在同一個寄存器上執行的操作便可以在臨時性的不同的寄存器上執行,無論讀寫,互不干擾 (注意:這裡要求兩個操作沒有數據依賴)。而對應的微操作的操作數也變為了臨時性的別名寄存器,相當於一種空間換時間的策略,並且同時對微指令進行了一次基於別名寄存器的轉譯。

之後微操作進入 Reorder Buffer。至此,微指令已經準備就緒。它們會被放入 Reservation Station(RS)並被並行執行。從圖中可以看到相當多的執行單元 (Port X)。每一個執行單元都執行一個特定的任務,比如讀取 (Load),寫入 (Store),整數計算(ALU, SEE)等等。而每一條相關的微指令都可以在它所需要的數據準備好之後執行。這樣耗時較長的指令和有數據依賴關係的指令,雖然單從其自身的角度看,並沒有任何變化,但它們所帶來的阻塞的開銷,被後續指令的並行及亂序(提前)執行所分攤,化整為零,帶來整體吞吐的提升。

亂序執行核心的神奇之處就在於,它能夠最大限度地提升這套機制的效率,並且在外界看來,指令是在順序執行。這裡面的詳細細節不在本文的討論範疇。但亂序執行核心是如此成功,以至於引入該機制的 CPU即便是在大工作負載的情況下亂序執行核心仍會在大部分時間處於空閑的狀態,遠未飽和。因此,又引入了另外一個前端 (Front-end,包括 Fetch和 Decode)給該核心輸送μ-ops,在系統看來,便可以抽象為兩個處理核心,這也就是超線程 (Hyper-thread)N個物理核心,2N個邏輯核心的由來。

亂序執行也並不一定 100%達到順序執行代碼的效果。有些時候確實需要程序員引入內存屏障來確保執行的先後順序。

但複雜的事物總會引入新的問題,這次矛盾轉移到了 Fetch階段。如何在面對分支的時候選取正確的路?如果指令選取錯誤,整條流水線需要首先等待剩餘指令執行完畢,清空之後再重新從正確的位置開始。流水線的層次越深,造成的傷害越大。後續的文章,將會介紹一些在編程層面優化的方法。

作者介紹

張攀,雲杉網路工程師,專註於 x86網路軟體的開發與性能優化,深度參與 ONF/OPNFV/ONOS等組織及社區,曾任 ONF測試工作組副主席。

時至今日,Google等國外巨頭已經跨過 DevOps轉為 SRE,而國內大多數公司卻仍在推進 DevOps的路上止步不前。也許只有行業頂尖的公司,或者新成立的公司會有這樣的嘗試。大多數的企業還未開始進行敏捷的推進,傳統的重重阻礙會使敏捷的推進進程遙遙無期。DevOps真的離我們有那麼遠嗎?DevOps應該從哪裡開始呢?

StuQ特別邀請 ThoughtWorks DevOps技術諮詢師林帆老師帶領大家從頭認識『DevOps』文化的起源,和你一起編寫漂亮的持續交付流水線,動手聯通從代碼開發、測試到上線,以及運行反饋的完整過程,實實在在地體驗一把 DevOps工程師的日常。讓你在一個多月的時間裡,快速點亮『DevOps』的核心技能樹。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 安卓網 的精彩文章:

樹大招風:榮耀手機「銷量榜登頂」,趙明卻大吐苦水?
曾經的面子手機,走在街上,拿在手裡。懷念一下
摔不壞砸不爛,比硬度,這款手機秒殺諾基亞!
科技晚報:魅族聯諾基亞搞事 崑崙萬維收購Grindr
蘋果手機發布十周年,史上銷量最好的機型,如今論與小米6一個價

TAG:安卓網 |

您可能感興趣

從價值觀的層面談談我對996的看法
不僅僅是顏值—談談不斷進化中的神鷹戰機
iOS13提前嘗鮮,升級體驗一天,談談真實優缺點!
4800萬相機,紅米推出首款水滴屏手機,談談上手感受
談談水平許可權繞過
換裝cup2,順道談談,是什麼讓你的剎車性能更好
談談植物在過濾中的作用,以及如何做到真正的不換水
從7月新番實例,再來談談演出對動畫作品表現力的影響
以76人為例,談談戰術的邏輯在哪
2999的小米9實際體驗怎麼樣?真機上手,談談它的優缺點!
談談面膜一般幾天敷一次 才能達到比較好的補水效果
我為什麼暫不買70邁胎壓監測儀——談談其最大賣點和需改進的地方
談談你訓練中多次提到的靈活性與穩定性
談談風水和積陰德
魅藍S6、紅米5Plus極速體驗84小時,談談有何不同感受
港水大師談風水——談談一些著名建築的「風水故事」
談談慢性萎縮性胃炎的管理
用了三個月的小米8之後,談談這款手機的缺點與優點
刺激戰場:談談更新後戰鬥的優化,M24正式宣布移出空投
談談考試頻率