在計算機體系中,數據並行有兩種實現路徑:MIMD(Multiple Instruction Multiple Data,多指令流多數據流)和SIMD(Single Instruction Multiple Data,單指令流多數據流)。其中MIMD的表現形式主要有多發射、多線程、多核心,在當代設計的以處理能力為目標驅動的處理器中,均能看到它們的身影。同時,隨着多媒體、大數據、人工智能等應用的興起,為處理器賦予SIMD處理能力變得愈發重要,因為這些應用存在大量細粒度、同質、獨立的數據操作,而SIMD天生就適合處理這些操作。
SIMD結構有三種變體:向量體系結構、多媒體SIMD指令集擴展和圖形處理單元。本文集中圍繞圖形處理單元( Graphics Processing Unit, GPU)進行描述。
圖形處理單元(GPU)
0. 寫在前面
GPU 獨立於 CPU 而發展,擁有自己的一套體系結構,並在發展中形成了自己的專門術語。加上不同設計廠商之間使用的術語往往不太一樣,容易給 GPU 初學者帶來名詞分辨上的困擾。
鑒於 NVIDIA GPU 的成功,本文只使用 NVIDIA 官方術語進行表述。我自己在看《計算機體系結構:量化方法研究》這本書時,也存在着不少困惑,有些名詞感覺前后表達的意思不容易對上號,甚至多看幾遍之后,困惑愈深。不過最終我還是理了一個基本不差的脈絡出來,如果有讀者發現我表述中差錯的地方,敬請指正。
1. 簡介
談到 GPU,人們首先想到的可能是「顯卡」,進而想到「英偉達(NVIDIA)」。的確, GPU 的祖先便是圖形加速器,用於輔助 CPU 做圖形處理的工作,而黃仁勛創辦的英偉達正是 GPU 領域的佼佼者,不斷引領着 GPU 架構的發展革新。
我們知道,圖形顯示是由一個個細小的像素組合而成,在計算機的世界中,像素的變化即意味着一組固定寬度比特位所表示的參數的變化。因此,對一副圖形進行變換,本質上是對成千上萬個像素的參數進行計算的結果,而這其中必然存在大量的數據並行度。
GPU 天生是處理並行問題的好手,在它的體系結構中融合了線程並行、指令並行、SIMD 數據並行的多種並行處理形式,它可以概括為是一個由多個多線程 SIMD 處理器組成的 MIMD 處理器。
GPU 處理數據並行任務能有很好的效能比,但處理控制為主的任務則不夠經濟,因此 GPU 一直以來是作為 CPU 的外設而存在的。隨着異構處理器的興起,二者常常被集成在同一個處理器芯片中,相輔相成。同時,GPU的計算能力已不局限於圖形處理,而是正在被遷移到更多的數據並行計算場景。
2. 架構組成
了解 GPU 比較適合先從一個大的角度看下去,然后往下深入到局部的部件。正所謂「窺一斑而現全身」,從圖1可以看到,GPU 幾乎是由眾多相同的部件復制組合而成。
圖1 Fermi GTX 480 GPU 的平面圖
從硬件抽象的角度看:
-
GPU,與 CPU 經總線連接通信,並通過片外 DRAM 交換數據。GPU 的主體由多個(16/32···)相同的流式多處理器組成。
-
流式多處理器(Streaming Multiproeessor,SM),它是具有獨立PC的完整處理器。每個流式多處理器擁有多個(16···)獨立 SIMD 線程,稱為「warp」。各 warp 不會同時執行,而是依據每個 warp 的忙閑狀態進行恰當的調度,這也正是 GPU 設計的基本思想:通過足夠多的線程切換來隱藏訪問 DRAM 的延遲。
-
warp,每個 warp 有自己的 PC,執行 SIMD 指令完成計算和訪存。SIMD 指令功能單元含有多個(16/32···)並行的線程處理器(或者說車道)
-
線程處理器,它執行一個 warp 中 SIMD 指令對應的單個元素,可根據謂詞寄存器或者遮罩位狀態,決定執行或忽略對應元素的操作。同一個 warp 中的多個車道不能同時執行不同指令。
從程序抽象的角度看:
-
網格,在 GPU 上運行,由一個或多個線程塊構成的代碼。
-
線程塊,在流式多處理器上執行的向量化循環,由一個或多個 SIMD指令線程構成。
-
CUDA 線程,SIMD 指令線程的垂直抽取,對應於車道所執行的單個元素。
GPU 擁有兩層硬件調度程序:
-
線程塊調度器(NVIDIA 稱 Giga 線程引擎),將線程塊指定給流式多處理器,確保線程塊被分配給其共享存儲器擁有相應數據的流式多處理器。
-
warp 調度器,流式多處理器內部的SIMD線程(warp)調度程序,由它來調度應該何時運行哪個warp。一個流式多處理器中可能同時存在兩個 warp 調度器。
圖2 warp 調度器分配指令給 warp 執行(圖中含兩個warp 調度器)
從存儲器的角度看:
-
全局存儲器,由GPU所有流式處理器共享,它是位於流式處理器片外的 DRAM。流式處理器間不能直接進行通信,但可以使用全局存儲器中的原子存儲操作進行協調。系統處理器(CPU)和 GPU 之間的數據交換也是通過全局存儲器。
-
共享存儲器,位於流式處理器內部,這一存儲器由單個流式處理器內部所有 warp 共享,但無法被其它流式處理器訪問。流式處理器內的 warp 可以通過共享存儲器進行通信。
-
局部存儲器,各 warp 在片外 DRAM 獲得的一個專用部分,它不能在同一流式處理器的各 warp 間共享。局部存儲器用於棧幀、溢出寄存器和不能放在寄存器中的私有變量。
-
寄存器,大量存在於 GPU中。由於每一個 warp 都是一個獨立的線程,因此各 warp 都分配有獨立的向量寄存器組,寄存器組中的每一個向量寄存器有N個元素(16/32···),N 為 GPU 的向量長度。
-
緩存,在 GPU 中的容量不如 CPU,因為 GPU 不依賴大型緩存來包含應用程序的整個工作集,而是使用較少的流式緩存,依靠大量的線程來隱藏訪問 DRAM 的長延遲。CPU 中用來放置cache的芯片面積在 GPU 中替換為計算資源和大量的寄存器,用來執行大量的 SIMD 線程。GPU 中的緩存要么被看作帶寬濾選器,以減少對全局存儲器的要求,要么被看作有限幾種變量的加速器,這些變量的修改不能通過多線程來隱藏。
圖3 GPU 存儲訪問關系
圖4 顯示了以上軟硬件之間的層級對應關系:
圖4 GPU 軟硬件間的層級對應關系
3. GPU的條件分支
相比於向量體系結構,GPU提供了更多的硬件機制來實現 warp 中的分支處理。GPU 用於處理分支的硬件有:謂詞寄存器、分支同步棧、指令標記、內部遮罩。
當條件分支較簡單時,編譯器將代碼編譯成僅使用謂詞寄存器的條件執行指令。謂詞被設置為1的車道將執行操作並存儲指令;其它車道將不執行和存儲結果。
當面對更復雜的控制流時,編譯器將混合使用謂詞寄存器和 GPU 分支指令。特殊的指令和標記將用於這些分支指令,在執行到這些分支指令時,先向分支同步棧中壓入一個棧項(主要為遮罩),隨后聯合謂詞寄存器的內容更新遮罩,在跳轉的目標分支中通過遮罩來決定對應車道是否執行操作和存儲;當分支結束,則彈出並恢復棧項。整個過程類似於函數調用過程中的現場保存和恢復機制,因此可實現多層分支的嵌套。
由於同一 warp 內的不同車道不能同時執行不同指令,使得 GPU 必須計算所有的分支,而各分支執行過程中必然存在部分車道執行、其它車道空閑的情況。這帶來了 GPU 工作效率的下降。比如,對於相同長度的路徑,if-then-else的工作效率為50%;對於嵌套的分支語句,這一效率將進一步降低。幸運的是, GPU 可以在運行時,在遮罩位為全0或全1時,略過 then 或 else 部分。
編程利器——CUDA
當 GPU 的計算潛力與一種簡化 GPU 編程的編程語言相結合,使人們對 GPU 計算的興趣大增。
編寫並行程序天生要比編寫順序程序復雜得多,因為編譯器只能自動並行化一些簡單的並行情況,大部分時候都需要編程者自己給出並行化的信息,並進行合理的調度,以使得並行化的效能最大化。
CUDA (Compute Unified Device Architecture,計算統一設備體系結構)就是 NVIDIA 開發的一種與 C 類似的語言和編程環境,通過克服異質計算 及多種並行帶來的雙重挑戰來提高 GPU 程序員的生產效率。
CUDA 並不只為 GPU 編程,而是把 CPU 和 GPU 當作一個整體來編程。CUDA 為系統處理器(主機)生成 C/C++,為 GPU(設備)生成 C/C++ 方言。
CUDA 將 CUDA 線程作為編程原型,表示最低級別的並行。編譯器和硬件可以將數以千計的 CUDA 線程聚合在一起,利用 GPU 中的多種並行類型:多線程、MIMD、SIMD、指令並行。
關於 CUDA 語言的具體語法以及編程技巧,是一個比較大的內容,本文只介紹其概念,不做贅述。后期等信號君學習了相關的內容,可以分一個系列再來和大家探討。
物理指令集抽象——PTX指令集
與大多數系統處理器不同,NVIDIA 編譯器的指令集目標是硬件指令集的一種抽象,NVIDIA 稱之為 PTX 指令集。
編譯器生成的指令與 PTX 指令一一對應,但一個 PTX 指令可以擴展到許多機器指令,反之亦然。PTX 使用虛擬寄存器,而不是實際的物理寄存器。從 PTX 指令集到 GPU 物理指令集的這一層抽象解釋由優化程序實現。優化程序在 warp 之間划分可用的寄存器,它還會清除死亡代碼,將指令打包在一起,並計算分支發生發散的位置和發散路徑可能會聚的位置。
之所以要使用 PTX 指令集,而不是直接使用物理指令集,是為了解決軟硬件工程中揮之不去的兼容性問題。PTX 的存在為編譯器提供了一種穩定的指令集,可以實現各代GPU 之間的兼容性。不同的 GPU 只需要適配相應的優化程序即可實現代碼兼容,這就給了 GPU 硬件非常大的變化空間。
PTX 抽象指令集的這一思想可以類比於 x86微體系結構。眾所周知,Intel 為了實現當代內核指令對以往版本內核代碼的兼容,在硬件上做了一個解釋層,將復雜指令翻譯成一組微指令,然后再進行真正的指令譯碼操作。它們的區別在於:x86 的這一轉換是在運行時以硬件方式實現,而 PTX 指令轉換為物理指令是在載入時以軟件實現的。
延遲優化 & 吞吐量優化
在研究計算機體系結構時,延遲和吞吐量是經常會被提及的兩個指標。「延遲」表示從發出一條指令/請求,到得到結果所經歷的時間;「吞吐量」表示單位時間內,完成的指令/請求數。
絕大部分的CPU 為了通用和低延遲做了更多優化,我們稱之為基於延遲優化的處理器。GPU為了高並發、高流水犧牲了通用性和延遲,我們稱之為基於吞吐量優化的處理器。
GPU 依賴於流式多處理器中的硬件多線程以隱藏訪存延時,但這並不意味着延遲不存在。例如,流式多處理器中的某個 warp 發起外部 DRAM 訪問,由於沒有緩存支持,這一訪問結果可能需要幾十個 cycle 后才能有效,於是該 warp 只能選擇等待,但在它等待的間隙,warp 調度器將執行處理器內的其它線程。盡管單個線程的訪存發生大量的延遲,但從流式多處理器整體而言,它的吞吐量還是很大。
基於延遲還是吞吐量來優化計算機系統並沒有一個絕對的優劣標准,而是從計算機系統的應用場景出發來考量。拿 GPU 的主要工作圖形處理來講,一般的圖形都是以幾十幀每秒的速率切換,因此 GPU 只需要以 ms 級的速率完成一幅圖像內容的處理即可滿足要求,這時候單個線程的延遲就顯得無關緊要了。
有一點值得注意的是:低延遲不意味着低吞吐量;高吞吐量也不意味着高延遲。延遲與吞吐量之間也沒有絕對的限制關系。一個計算機系統它可以是低延遲的同時,具備高的吞儲量。
參考資料
【1】John L. Hennessy,David A. Patterson . 計算機體系結構:量化研究方法:第5版[M].北京:人民郵電出版社,2013. (原名《Computer Architecture:A Quantitative Approach》)
【2】David A. Patterson,John L. Hennessy . 計算機組成與設計:硬件/軟件接口:第5版[M].北京:機械工業出版社,2015. (原名《Computer Organization and Design:The Hardware/Software Interface》)
·END·
你可能還感興趣:
歡迎來我的微信公眾號做客:信號君
專注於信號處理知識、高性能計算、現代處理器&計算機體系
技術成長 | 讀書筆記 | 認知升級
幸會~