GPU的並行運算與CUDA的簡介

古來聖賢皆寂寞發表於2019-01-26

 一:GPU 程式設計技術的發展歷程及現狀

1.馮諾依曼計算機架構的瓶頸

  曾經,幾乎所有的處理器都是以馮諾依曼計算機架構為基礎的。該系統架構簡單來說就是處理器從儲存器中不斷取指,解碼,執行。

       但如今這種系統架構遇到了瓶頸:記憶體的讀寫速度跟不上 CPU 時脈頻率。具有此特徵的系統被稱為記憶體受限型系統,目前的絕大多數計算機系統都屬於此型別。

       為了解決此問題,傳統解決方案是使用快取技術。通過給 CPU 設立多級快取,能大大地降低儲存系統的壓力:

       然而隨著快取容量的增大,使用更大快取所帶來的收益增速會迅速下降,這也就意味著我們要尋找新的辦法了。

2.對 GPU 程式設計技術發展具有啟發意義的幾件事

1. 70年代末期,克雷系列超級計算機研製成功 (克雷1當年耗資800萬美元)。

       此類計算機採用若干記憶體條的共享記憶體結構,即這些記憶體條可以與多個處理器相連線,從而發展成今天的對稱多處理器系統 (SMD)。

       克雷2是向量機 - 一個操作處理多個運算元。

       如今的 GPU 裝置的核心也正是向量處理器。

2. 80年代初期,一家公司設計並研製了一種被稱為連線機的計算機系統。

       該系統具有16個 CPU 核,採用的是標準的單指令多資料 (SIMD) 並行處理。連線機通過這種設計能夠消除多餘的訪存操作,並將記憶體讀寫週期變為原來的 1/16 。

3. CELL 處理器的發明

       這類處理器很有意思,其架構大致如下圖所示:

       在此結構中,一個 PPC 處理器作為監管處理器,與大量的 SPE流處理器相連通,組成了一個工作流水線。

       對於一個圖形處理過程來說,某個 SPE 可負責提取資料,另一個 SPE 負責變換,再另一個負責存回。這樣可構成一道完完整整的流水線,大大提高了處理速度。

       順便提一句,2010年超級計算機排名第三的計算機就是基於這種設計理念實現的,佔地面積達560平方米,耗資 1.25 億美元。

3.多點計算模型

  叢集計算是指通過將多個效能一般的計算機組成一個運算網路,達到高效能運算的目的。這是一種典型的多點計算模型。而 GPU 的本質,也同樣是多點計算模型。

其相對於當今比較火的Hadoop/Spark叢集來說:“點”由單個計算機變成了 單個SM (流處理器簇),通過網路互連變成了通過視訊記憶體互連 (多點計算模型中點之間的通訊永遠是要考慮的重要問題)。

4.GPU 解決方案

 隨著 CPU "功耗牆" 問題的產生,GPU 解決方案開始正式走上舞臺。

       GPU 特別適合用於平行計算浮點型別的情況,下圖展示了這種情況下 GPU 和 CPU 計算能力的差別:

       但這可不能說明 GPU 比 CPU 更好,CPU應當被淘汰。 上圖的測試是在計算可完全並行的情況下進行的。

       對於邏輯更靈活複雜的序列程式,GPU 執行起來則遠不如 CPU 高效 (沒有分支預測等高階機制)。

       另外,GPU 的應用早已不侷限於影象處理。事實上 CUDA 目前的高階板卡 Tesla 系列就是專門用來進行科學計算的,它們連 VGA 介面都沒。

5.主流 GPU 程式設計介面

   1. CUDA

       是英偉達公司推出的,專門針對 N 卡進行 GPU 程式設計的介面。文件資料很齊全,幾乎適用於所有 N 卡。

       本專欄講述的 GPU 程式設計技術均基於此介面。

       2. Open CL

       開源的 GPU 程式設計介面,使用範圍最廣,幾乎適用於所有的顯示卡。

       但相對 CUDA,其掌握較難一些,建議先學 CUDA,在此基礎上進行 Open CL 的學習則會非常簡單輕鬆。

       3. DirectCompute

       微軟開發出來的 GPU 程式設計介面。功能很強大,學習起來也最為簡單,但只能用於 Windows 系統,在許多高階伺服器都是 UNIX 系統無法使用。

       總結,這幾種介面各有優劣,需要根據實際情況選用。但它們使用起來方法非常相近,掌握了其中一種再學習其他兩種會很容易。

二:從 GPU 的角度理解平行計算

1.平行計算中需要考慮的三個重要問題

       1. 同步問題

       在作業系統原理的相關課程中我們學習過程式間的死鎖問題,以及由於資源共享帶來的臨界資源問題等,這裡不做累述。

  2. 併發度

       有一些問題屬於 “易並行” 問題:如矩陣乘法。在這型別問題中,各個運算單元輸出的結果是相互獨立的,這類問題能夠得到很輕鬆的解決 (通常甚至呼叫幾個類庫就能搞定問題)。

       然而,若各個運算單元之間有依賴關係,那問題就複雜了。在 CUDA 中,塊內的通訊通過共享記憶體來實現,而塊間的通訊,則只能通過全域性記憶體。

       CUDA 並行程式設計架構可以用網格 (GRID) 來形容:一個網格好比一隻軍隊。網格被分成好多個塊,這些塊好比軍隊的每個部門 (後勤部,指揮部,通訊部等)。每個塊又分成好多個執行緒束,這些執行緒束好比部門內部的小分隊,下圖可幫助理解:

       3. 區域性性

       在作業系統原理中,對區域性性做過重點介紹,簡單來說就是將之前訪問過的資料 (時間區域性性) 和之前訪問過的資料的附近資料 (空間區域性性) 儲存在快取中。

       在 GPU 程式設計中,區域性性也是非常重要的,這體現在要計算的資料應當在計算之前儘可能的一次性的送進視訊記憶體,在迭代的過程中一定要儘可能減少資料在記憶體和視訊記憶體之間的傳輸,實際專案中發現這點十分重要的。

       對於 GPU 程式設計來說,需要程式猿自己去管理記憶體,或者換句話來說,自己實現區域性性。

2.平行計算的兩種型別

       1. 基於任務的並行處理

       這種並行模式將計算任務拆分成若干個小的但不同的任務,如有的運算單元負責取數,有的運算單元負責計算,有的負責...... 這樣一個大的任務可以組成一道流水線。

       需要注意的是流水線的效率瓶頸在於其中效率最低的那個計算單元。

  2. 基於資料的並行處理

       這種並行模式將資料分解為多個部分,讓多個運算單元分別去計算這些小塊的資料,最後再將其彙總起來。

       一般來說,CPU 的多執行緒程式設計偏向於第一種並行模式,GPU 並行程式設計模式則偏向於第二種。

3.常見的並行優化物件

       1. 迴圈

       這也是最常見的一種模式,讓每個執行緒處理迴圈中的一個或一組資料。

       這種型別的優化一定要小心各個運算單元,以及每個運算單元何其自身上一次迭代結果的依賴性。

       2. 派生/彙集模式

       該模式下大多數是序列程式碼,但程式碼中的某一段可以並行處理。

       典型的情況就是某個輸入佇列當序列處理到某個時刻,需要對其中不同部分進行不同處理,這樣就可以劃分成多個計算單元對改佇列進行處理 (也即派生),最後再將其彙總 (也即彙集)。

       這種模式常用於併發事件事先不定的情況,具有 “動態並行性”。

       3. 分條/分塊模式

       對於特別龐大的資料 (如氣候模型),可以將資料分為過個塊來進行平行計算。

       4. 分而治之

       絕大多數的遞迴演算法,比如快速排序,都可以轉換為迭代模型,而迭代模型又能對映到 GPU 程式設計模型上。

       特別說明:雖然費米架構和開普勒架構的 GPU 都支援緩衝棧,能夠直接實現遞迴模型到 GPU 並行模型的轉換。但為了程式的效率,在開發時間允許的情況下,我們最好還是先將其轉換為迭代模型。

 

GPU 並行程式設計的核心在於執行緒,一個執行緒就是程式中的一個單一指令流,一個個執行緒組合在一起就構成了平行計算網格,成為了並行的程式,下圖展示了多核 CPU 與 GPU 的計算網格:

我們前面已經大概的介紹了CUDA執行模型的大概過程,包括執行緒網格,執行緒束,執行緒間的關係,以及硬體的大概結構,例如SM的大概結構,而對於硬體來說,CUDA執行的實質是執行緒束的執行,因為硬體根本不知道每個塊誰是誰,也不知道先後順序,硬體(SM)只知道按照機器碼跑,而給他什麼,先後順序,這個就是硬體功能設計的直接體現了。
從外表來看,CUDA執行所有的執行緒,並行的,沒有先後次序的,但實際上硬體資源是有限的,不可能同時執行百萬個執行緒,所以從硬體角度來看,物理層面上執行的也只是執行緒的一部分,而每次執行的這一部分,就是我們前面提到的執行緒束。

執行緒束和執行緒塊

執行緒束是SM中基本的執行單元,當一個網格被啟動(網格被啟動,等價於一個核心被啟動,每個核心對應於自己的網格),網格中包含執行緒塊,執行緒塊被分配到某一個SM上以後,將分為多個執行緒束,每個執行緒束一般是32個執行緒(目前的GPU都是32個執行緒,但不保證未來還是32個)在一個執行緒束中,所有執行緒按照單指令多執行緒SIMT的方式執行,每一步執行相同的指令,但是處理的資料為私有的資料,下圖反應的就是邏輯,實際,和硬體的圖形化

執行緒塊是個邏輯產物,因為在計算機裡,記憶體總是一維線性存在的,所以執行起來也是一維的訪問執行緒塊中的執行緒,但是我們在寫程式的時候卻可以以二維三維的方式進行,原因是方便我們寫程式,比如處理影象或者三維的資料,三維塊就會變得很直接,很方便。
在塊中,每個執行緒有唯一的編號(可能是個三維的編號),threadIdx。
網格中,每個執行緒塊也有唯一的編號(可能是個三維的編號),blockIdx
那麼每個執行緒就有在網格中的唯一編號。
當一個執行緒塊中有128個執行緒的時候,其分配到SM上執行時,會分成4個塊:

warp0: thread  0,........thread31
warp1: thread 32,........thread63
warp2: thread 64,........thread95
warp3: thread 96,........thread127

 

 

   該圖表示,計算網格由多個流處理器構成,每個流處理器又包含 n 多塊。

      下面進一步對 GPU 計算網格中的一些概念做細緻分析。

      1. 執行緒

      執行緒是 GPU 運算中的最小執行單元,執行緒能夠完成一個最小的邏輯意義操作。

      2. 執行緒束

      執行緒束是 GPU 中的基本執行單元。GPU 是一組 SIMD 處理器的集合,因此每個執行緒束中的執行緒是同時執行的。這個概念是為了隱藏對視訊記憶體進行讀寫帶來的延遲所引入的。

      目前英偉達公司的顯示卡此值為 32,不可改動,也不應該對其進行改動。

      3. 執行緒塊

      一個執行緒塊包含多個執行緒束,在一個執行緒塊內的所有執行緒,都可以使用共享記憶體來進行通訊、同步。但一個執行緒塊能擁有的最大執行緒/執行緒束,和顯示卡型號有關。

      4. 流多處理器

      流多處理器就相當於 CPU 中的核,負責執行緒束的執行。同一時刻只能有一個執行緒束執行。

      5. 流處理器

      流處理器只負責執行執行緒,結構相對簡單。

GPU 和 CPU 在平行計算方面的不同

      1. 任務數量

      CPU 適合比較少量的任務,而 GPU 則適合做大量的任務。

      2. 任務複雜度

      CPU 適合邏輯比較複雜的任務,而 GPU 則適合處理邏輯上相對簡單的任務 (可用比較少的語句描述)。

      3. 執行緒支援方式

      由於 CPU 中執行緒的暫存器組是公用的,因此CPU 在切換執行緒的時候,會將執行緒的暫存器內容儲存在 RAM 中,當執行緒再次啟動的時候則會從 RAM 中恢復資料到暫存器。

      而 GPU 中的各個執行緒則各自擁有其自身的暫存器組,因此其切換速度會快上不少。

      當然,對於單個的執行緒處理能力來說,CPU 更強。

      4. 處理器分配原則

      CPU 一般是基於時間片輪轉排程原則,每個執行緒固定地執行單個時間片;而 GPU 的策略則是線上程阻塞的時候迅速換入換出。

      5. 資料吞吐量

      GPU 中的每個流處理器就相當於一個 CPU 核,一個 GPU 一般具有 16 個流處理器,而且每個流處理器一次能計算 32 個數。

CUDA是用於GPU計算的開發環境,它是一個全新的軟硬體架構,可以將GPU視為一個並行資料計算的裝置,對所進行的計算進行分配和管理。在CUDA的架構中,這些計算不再像過去所謂的GPGPU架構那樣必須將計算對映到圖形API(OpenGL和Direct 3D)中,因此對於開發者來說,CUDA的開發門檻大大降低了。CUDA的GPU程式語言基於標準的C語言,因此任何有C語言基礎的使用者都很容易地開發CUDA的應用程式。

與CUDA相關的幾個概念:thread,block,grid,warp,sp,sm。

sp: 最基本的處理單元,streaming processor  最後具體的指令和任務都是在sp上處理的。GPU進行平行計算,也就是很多個sp同時做處理

sm:多個sp加上其他的一些資源組成一個sm,  streaming multiprocessor. 其他資源也就是儲存資源,共享記憶體,寄儲器等。

warp:GPU執行程式時的排程單位,目前cuda的warp的大小為32,同在一個warp的執行緒,以不同資料資源執行相同的指令。

grid、block、thread:在利用cuda進行程式設計時,一個grid分為多個block,而一個block分為多個thread.其中任務劃分到是否影響最後的執行效果。劃分的依據是任務特性和GPU本身的硬體特性。

 

相關文章