GPU(CUDA)學習日記(十一)------ 深入理解CUDA線程層次以及關於設置線程數的思考

       GPU線程以網格(grid)的方式組織,而每個網格中又包含若干個線程塊,在G80/GT200系列中,每一個線程塊最多可包含512個線程,Fermi架構中每個線程塊支持高達1536個線程。同一線程塊中的衆多線程擁有相同的指令地址,不僅能夠並行執行,而且能夠通過共享存儲器(Shared memory)和柵欄(barrier)實現塊內通信。這樣,同一網格內的不同塊之間存在不需要通信的粗粒度並行,而一個塊內的線程之間又形成了允許通信的細粒度並行。這些就是CUDA的關鍵特性:線程按照粗粒度的線程塊和細粒度的線程兩個層次進行組織、在細粒度並行的層次通過共享存儲器和柵欄同步實現通信,這就是CUDA的雙層線程模型。
       在執行時,GPU的任務分配單元(global block scheduler)將網格分配到GPU芯片上。啓動CUDA 內核時,需要將網格信息從CPU傳輸到GPU。任務分配單元根據這些信息將塊分配到SM上。任務分配單元使用的是輪詢策略:輪詢查看SM是否還有足夠的資源來執行新的塊,如果有則給SM分配一個新的塊,如果沒有則查看下一個SM。決定能否分配的因素有:每個塊使用的共享存儲器數量,每個塊使用的寄存器數量,以及其它的一些限制條件。任務分配單元在SM的任務分配中保持平衡,但是程序員可以通過更改塊內線程數,每個線程使用的寄存器數和共享存儲器數來隱式的控制,從而保證SM之間的任務均衡。任務以這種方式劃分能夠使程序獲得了可擴展性:由於每個子問題都能在任意一個SM上運行,CUDA程序在覈心數量不同的處理器上都能正常運行,這樣就隱藏了硬件差異。
       對於程序員來說,他們需要將任務劃分爲互不相干的粗粒度子問題(最好是易並行計算),再將每個子問題劃分爲能夠使用線程處理的問題。同一線程塊中的線程開始於相同的指令地址,理論上能夠以不同的分支執行。但實際上,在塊內的分支因爲SM構架的原因被大大限制了。內核函數實質上是以塊爲單位執行的。同一線程塊中的線程需要SM中的共享存儲器共享數據,因此它們必須在同一個SM中發射。線程塊中的每一個線程被髮射到一個SP上。任務分配單元可以爲每個SM分配最多8個塊。而SM中的線程調度單元又將分配到的塊進行細分,將其中的線程組織成更小的結構,稱爲線程束(warp)。在CUDA中,warp對程序員來說是透明的,它的大小可能會隨着硬件的發展發生變化,在當前版本的CUDA中,每個warp是由32個線程組成的。SM中一條指令的延遲最小爲4個指令週期。8個SP採用了發射一次指令,執行4次的流水線結構。所以由32個線程組成的Warp是CUDA程序執行的最小單位,並且同一個warp是嚴格串行的,因此在warp內是無須同步的。在一個SM中可能同時有來自不同塊的warp。當一個塊中的warp在進行訪存或者同步等高延遲操作時,另一個塊可以佔用SM中的計算資源。這樣,在SM內就實現了簡單的亂序執行。不同塊之間的執行沒有順序,完全並行。無論是在一次只能處理一個線程塊的GPU上,還是在一次能處理數十乃至上百個線程塊的GPU上,這一模型都能很好的適用。

       目前,某一時刻只能有一個內核函數正在執行,但是在Fermi架構中,這一限制已被解除。如果在一個內核訪問數據時,另一個內核能夠進行計算,則可以有效的提高設備的利用率。

       每一個塊內線程數應該首先是32的倍數,因爲這樣的話可以適應每一個warp包含32個線程的要求,每一個warp中串行執行,這就要求每一個線程中不可以有過多的循環或者需要的資源過多。但是每一個塊中如果線程數過多,可能由於線程中參數過多帶來存儲器要求過大,從而使SM處理的效率更低。所以,在函數不是很複雜的情況下,可以適當的增加線程數目,線程中不要加入循環。在函數比較複雜的情況下,每一個塊中分配32或是64個線程比較合適。每一個SM同時處理一個塊,只有在粗粒度層面上以及細粒度層面上均達到平衡,才能使得GPU的利用到達最大。我用的顯卡爲GeForce GTX560 Ti,每一個網格中允許的最大塊數位65535個,而每個塊中的線程數爲1024個,所以說粗粒度平衡對於我來說影響比較小,就細粒度來說,每一個塊中的線程數以及每一個線程中的循環就變得至關重要了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章