一:GPU 編程技術的發(fā)展歷程及現(xiàn)狀
1.馮諾依曼計算機架構的瓶頸
曾經(jīng),幾乎所有的處理器都是以馮諾依曼計算機架構為基礎的。該系統(tǒng)架構簡單來說就是處理器從存儲器中不斷取指,解碼,執(zhí)行。
但如今這種系統(tǒng)架構遇到了瓶頸:內(nèi)存的讀寫速度跟不上 CPU 時鐘頻率。具有此特征的系統(tǒng)被稱為內(nèi)存受限型系統(tǒng),目前的絕大多數(shù)計算機系統(tǒng)都屬于此類型。
為了解決此問題,傳統(tǒng)解決方案是使用緩存技術。通過給 CPU 設立多級緩存,能大大地降低存儲系統(tǒng)的壓力:

然而隨著緩存容量的增大,使用更大緩存所帶來的收益增速會迅速下降,這也就意味著我們要尋找新的辦法了。
2.對 GPU 編程技術發(fā)展具有啟發(fā)意義的幾件事
1. 70年代末期,克雷系列超級計算機研制成功 (克雷1當年耗資800萬美元)。
此類計算機采用若干內(nèi)存條的共享內(nèi)存結構,即這些內(nèi)存條可以與多個處理器相連接,從而發(fā)展成今天的對稱多處理器系統(tǒng) (SMD)。
克雷2是向量機 - 一個操作處理多個操作數(shù)。
如今的 GPU 設備的核心也正是向量處理器。
2. 80年代初期,一家公司設計并研制了一種被稱為連接機的計算機系統(tǒng)。
該系統(tǒng)具有16個 CPU 核,采用的是標準的單指令多數(shù)據(jù) (SIMD) 并行處理。連接機通過這種設計能夠消除多余的訪存操作,并將內(nèi)存讀寫周期變?yōu)樵瓉淼?1/16 。
3. CELL 處理器的發(fā)明
這類處理器很有意思,其架構大致如下圖所示:

在此結構中,一個 PPC 處理器作為監(jiān)管處理器,與大量的 SPE流處理器相連通,組成了一個工作流水線。
對于一個圖形處理過程來說,某個 SPE 可負責提取數(shù)據(jù),另一個 SPE 負責變換,再另一個負責存回。這樣可構成一道完完整整的流水線,大大提高了處理速度。
順便提一句,2010年超級計算機排名第三的計算機就是基于這種設計理念實現(xiàn)的,占地面積達560平方米,耗資 1.25 億美元。
3.多點計算模型
集群計算是指通過將多個性能一般的計算機組成一個運算網(wǎng)絡,達到高性能計算的目的。這是一種典型的多點計算模型。而 GPU 的本質,也同樣是多點計算模型。
其相對于當今比較火的Hadoop/Spark集群來說:“點”由單個計算機變成了 單個SM (流處理器簇),通過網(wǎng)絡互連變成了通過顯存互連 (多點計算模型中點之間的通信永遠是要考慮的重要問題)。
4.GPU 解決方案
隨著 CPU "功耗墻" 問題的產(chǎn)生,GPU 解決方案開始正式走上舞臺。
GPU 特別適合用于并行計算浮點類型的情況,下圖展示了這種情況下 GPU 和 CPU 計算能力的差別:

但這可不能說明 GPU 比 CPU 更好,CPU應當被淘汰。 上圖的測試是在計算可完全并行的情況下進行的。
對于邏輯更靈活復雜的串行程序,GPU 執(zhí)行起來則遠不如 CPU 高效 (沒有分支預測等高級機制)。
另外,GPU 的應用早已不局限于圖像處理。事實上 CUDA 目前的高端板卡 Tesla 系列就是專門用來進行科學計算的,它們連 VGA 接口都沒。
5.主流 GPU 編程接口
1. CUDA
是英偉達公司推出的,專門針對 N 卡進行 GPU 編程的接口。文檔資料很齊全,幾乎適用于所有 N 卡。
本專欄講述的 GPU 編程技術均基于此接口。
2. Open CL
開源的 GPU 編程接口,使用范圍最廣,幾乎適用于所有的顯卡。
但相對 CUDA,其掌握較難一些,建議先學 CUDA,在此基礎上進行 Open CL 的學習則會非常簡單輕松。
3. DirectCompute
微軟開發(fā)出來的 GPU 編程接口。功能很強大,學習起來也最為簡單,但只能用于 Windows 系統(tǒng),在許多高端服務器都是 UNIX 系統(tǒng)無法使用。
總結,這幾種接口各有優(yōu)劣,需要根據(jù)實際情況選用。但它們使用起來方法非常相近,掌握了其中一種再學習其他兩種會很容易。
二:從 GPU 的角度理解并行計算
1.并行計算中需要考慮的三個重要問題
1. 同步問題
在操作系統(tǒng)原理的相關課程中我們學習過進程間的死鎖問題,以及由于資源共享帶來的臨界資源問題等,這里不做累述。
2. 并發(fā)度
有一些問題屬于 “易并行” 問題:如矩陣乘法。在這類型問題中,各個運算單元輸出的結果是相互獨立的,這類問題能夠得到很輕松的解決 (通常甚至調用幾個類庫就能搞定問題)。
然而,若各個運算單元之間有依賴關系,那問題就復雜了。在 CUDA 中,塊內(nèi)的通信通過共享內(nèi)存來實現(xiàn),而塊間的通信,則只能通過全局內(nèi)存。
CUDA 并行編程架構可以用網(wǎng)格 (GRID) 來形容:一個網(wǎng)格好比一只軍隊。網(wǎng)格被分成好多個塊,這些塊好比軍隊的每個部門 (后勤部,指揮部,通信部等)。每個塊又分成好多個線程束,這些線程束好比部門內(nèi)部的小分隊,下圖可幫助理解:

3. 局部性
在操作系統(tǒng)原理中,對局部性做過重點介紹,簡單來說就是將之前訪問過的數(shù)據(jù) (時間局部性) 和之前訪問過的數(shù)據(jù)的附近數(shù)據(jù) (空間局部性) 保存在緩存中。
在 GPU 編程中,局部性也是非常重要的,這體現(xiàn)在要計算的數(shù)據(jù)應當在計算之前盡可能的一次性的送進顯存,在迭代的過程中一定要盡可能減少數(shù)據(jù)在內(nèi)存和顯存之間的傳輸,實際項目中發(fā)現(xiàn)這點十分重要的。
對于 GPU 編程來說,需要程序猿自己去管理內(nèi)存,或者換句話來說,自己實現(xiàn)局部性。
2.并行計算的兩種類型
1. 基于任務的并行處理
這種并行模式將計算任務拆分成若干個小的但不同的任務,如有的運算單元負責取數(shù),有的運算單元負責計算,有的負責...... 這樣一個大的任務可以組成一道流水線。
需要注意的是流水線的效率瓶頸在于其中效率最低的那個計算單元。
2. 基于數(shù)據(jù)的并行處理
這種并行模式將數(shù)據(jù)分解為多個部分,讓多個運算單元分別去計算這些小塊的數(shù)據(jù),最后再將其匯總起來。
一般來說,CPU 的多線程編程偏向于第一種并行模式,GPU 并行編程模式則偏向于第二種。
3.常見的并行優(yōu)化對象
1. 循環(huán)
這也是最常見的一種模式,讓每個線程處理循環(huán)中的一個或一組數(shù)據(jù)。
這種類型的優(yōu)化一定要小心各個運算單元,以及每個運算單元何其自身上一次迭代結果的依賴性。
2. 派生/匯集模式
該模式下大多數(shù)是串行代碼,但代碼中的某一段可以并行處理。
典型的情況就是某個輸入隊列當串行處理到某個時刻,需要對其中不同部分進行不同處理,這樣就可以劃分成多個計算單元對改隊列進行處理 (也即派生),最后再將其匯總 (也即匯集)。
這種模式常用于并發(fā)事件事先不定的情況,具有 “動態(tài)并行性”。
3. 分條/分塊模式
對于特別龐大的數(shù)據(jù) (如氣候模型),可以將數(shù)據(jù)分為過個塊來進行并行計算。
4. 分而治之
絕大多數(shù)的遞歸算法,比如快速排序,都可以轉換為迭代模型,而迭代模型又能映射到 GPU 編程模型上。
特別說明:雖然費米架構和開普勒架構的 GPU 都支持緩沖棧,能夠直接實現(xiàn)遞歸模型到 GPU 并行模型的轉換。但為了程序的效率,在開發(fā)時間允許的情況下,我們最好還是先將其轉換為迭代模型。
GPU 并行編程的核心在于線程,一個線程就是程序中的一個單一指令流,一個個線程組合在一起就構成了并行計算網(wǎng)格,成為了并行的程序,下圖展示了多核 CPU 與 GPU 的計算網(wǎng)格:

我們前面已經(jīng)大概的介紹了CUDA執(zhí)行模型的大概過程,包括線程網(wǎng)格,線程束,線程間的關系,以及硬件的大概結構,例如SM的大概結構,而對于硬件來說,CUDA執(zhí)行的實質是線程束的執(zhí)行,因為硬件根本不知道每個塊誰是誰,也不知道先后順序,硬件(SM)只知道按照機器碼跑,而給他什么,先后順序,這個就是硬件功能設計的直接體現(xiàn)了。 從外表來看,CUDA執(zhí)行所有的線程,并行的,沒有先后次序的,但實際上硬件資源是有限的,不可能同時執(zhí)行百萬個線程,所以從硬件角度來看,物理層面上執(zhí)行的也只是線程的一部分,而每次執(zhí)行的這一部分,就是我們前面提到的線程束。
線程束和線程塊
線程束是SM中基本的執(zhí)行單元,當一個網(wǎng)格被啟動(網(wǎng)格被啟動,等價于一個內(nèi)核被啟動,每個內(nèi)核對應于自己的網(wǎng)格),網(wǎng)格中包含線程塊,線程塊被分配到某一個SM上以后,將分為多個線程束,每個線程束一般是32個線程(目前的GPU都是32個線程,但不保證未來還是32個)在一個線程束中,所有線程按照單指令多線程SIMT的方式執(zhí)行,每一步執(zhí)行相同的指令,但是處理的數(shù)據(jù)為私有的數(shù)據(jù),下圖反應的就是邏輯,實際,和硬件的圖形化

線程塊是個邏輯產(chǎn)物,因為在計算機里,內(nèi)存總是一維線性存在的,所以執(zhí)行起來也是一維的訪問線程塊中的線程,但是我們在寫程序的時候卻可以以二維三維的方式進行,原因是方便我們寫程序,比如處理圖像或者三維的數(shù)據(jù),三維塊就會變得很直接,很方便。 在塊中,每個線程有唯一的編號(可能是個三維的編號),threadIdx。 網(wǎng)格中,每個線程塊也有唯一的編號(可能是個三維的編號),blockIdx 那么每個線程就有在網(wǎng)格中的唯一編號。 當一個線程塊中有128個線程的時候,其分配到SM上執(zhí)行時,會分成4個塊:
warp0: thread 0,........thread31
warp1: thread 32,........thread63
warp2: thread 64,........thread95
warp3: thread 96,........thread127

該圖表示,計算網(wǎng)格由多個流處理器構成,每個流處理器又包含 n 多塊。
下面進一步對 GPU 計算網(wǎng)格中的一些概念做細致分析。
1. 線程
線程是 GPU 運算中的最小執(zhí)行單元,線程能夠完成一個最小的邏輯意義操作。
2. 線程束
線程束是 GPU 中的基本執(zhí)行單元。GPU 是一組 SIMD 處理器的集合,因此每個線程束中的線程是同時執(zhí)行的。這個概念是為了隱藏對顯存進行讀寫帶來的延遲所引入的。
目前英偉達公司的顯卡此值為 32,不可改動,也不應該對其進行改動。
3. 線程塊
一個線程塊包含多個線程束,在一個線程塊內(nèi)的所有線程,都可以使用共享內(nèi)存來進行通信、同步。但一個線程塊能擁有的最大線程/線程束,和顯卡型號有關。
4. 流多處理器
流多處理器就相當于 CPU 中的核,負責線程束的執(zhí)行。同一時刻只能有一個線程束執(zhí)行。
5. 流處理器
流處理器只負責執(zhí)行線程,結構相對簡單。
GPU 和 CPU 在并行計算方面的不同
1. 任務數(shù)量
CPU 適合比較少量的任務,而 GPU 則適合做大量的任務。
2. 任務復雜度
CPU 適合邏輯比較復雜的任務,而 GPU 則適合處理邏輯上相對簡單的任務 (可用比較少的語句描述)。
3. 線程支持方式
由于 CPU 中線程的寄存器組是公用的,因此CPU 在切換線程的時候,會將線程的寄存器內(nèi)容保存在 RAM 中,當線程再次啟動的時候則會從 RAM 中恢復數(shù)據(jù)到寄存器。
而 GPU 中的各個線程則各自擁有其自身的寄存器組,因此其切換速度會快上不少。
當然,對于單個的線程處理能力來說,CPU 更強。
4. 處理器分配原則
CPU 一般是基于時間片輪轉調度原則,每個線程固定地執(zhí)行單個時間片;而 GPU 的策略則是在線程阻塞的時候迅速換入換出。
5. 數(shù)據(jù)吞吐量
GPU 中的每個流處理器就相當于一個 CPU 核,一個 GPU 一般具有 16 個流處理器,而且每個流處理器一次能計算 32 個數(shù)。
CUDA是用于GPU計算的開發(fā)環(huán)境,它是一個全新的軟硬件架構,可以將GPU視為一個并行數(shù)據(jù)計算的設備,對所進行的計算進行分配和管理。在CUDA的架構中,這些計算不再像過去所謂的GPGPU架構那樣必須將計算映射到圖形API(OpenGL和Direct 3D)中,因此對于開發(fā)者來說,CUDA的開發(fā)門檻大大降低了。CUDA的GPU編程語言基于標準的C語言,因此任何有C語言基礎的用戶都很容易地開發(fā)CUDA的應用程序。
與CUDA相關的幾個概念:thread,block,grid,warp,sp,sm。
sp: 最基本的處理單元,streaming processor 最后具體的指令和任務都是在sp上處理的。GPU進行并行計算,也就是很多個sp同時做處理
sm:多個sp加上其他的一些資源組成一個sm, streaming multiprocessor. 其他資源也就是存儲資源,共享內(nèi)存,寄儲器等。
warp:GPU執(zhí)行程序時的調度單位,目前cuda的warp的大小為32,同在一個warp的線程,以不同數(shù)據(jù)資源執(zhí)行相同的指令。
grid、block、thread:在利用cuda進行編程時,一個grid分為多個block,而一個block分為多個thread.其中任務劃分到是否影響最后的執(zhí)行效果。劃分的依據(jù)是任務特性和GPU本身的硬件特性。

|