GPU的並行運算與CUDA的簡介


 一: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本身的硬件特性。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM