CUDA 學習(四)、線程

一、概述

       英偉達的硬件調度是採用SPMD(單程序多數據),屬於SIMD(單指令多數據)的一種變體。線程是並行程序的基本構建塊。


二、CPU與GPU的差異

       GPU和CPU設備的架構是迥異的。CPU的設計是用來運行少量比較複雜的任務。GPU的設計則是用來運行大量比較簡單的任務。CPU的設計主要是針對大量離散而不相關任務系統。而GPU的設計主要是針對解決那些可以解決成千上萬個小塊並可獨立運行的問題。因此,CPU適合運行操作系統和應用程序軟件。

        CPU與GPU支持線程的方式不同。CPU的每個核只有少量的寄存器,每個寄存器都將在執行任何已分配的任務中被用到。爲了能執行不同的任務,CPU將在任務與任務之間進行上下文切換。從時間的角度來看,CPU上下文切換的代價是非常昂貴的,因爲每一次上下文切換都要將寄存器裏的數據保存到RAM中,等到重新執行這個任務時,又從RAM中恢復。相比之下,GPU同樣用到上下文切換隻需設置一個寄存器組調度者,用於將當前寄存器組裏的內容換進、換出,它的速度比將數據保存到RAM中要快好幾個數量級。

       CPU和GPU的一個主要差異就是每臺設備上處理器數量的巨大差異。CPU是典型的雙核或四核設備。也就是說它有一定數量的執行核可供程序運行。而目前費米架構的GPU擁有16個SM(流多處理器簇),每個SM可以看作是CPU的一個核。GPU默認就是並行的模式。GPU爲每個SM提供了唯一併且高速的寄存器,即共享內存。


三、任務執行模式

GPU的指令隊列中的每條指令都會分配到SM的每個SP中。每個SM就相當於嵌入了N個計算核心(SP)的處理器。GPU所用的SPMD模式是將同一條指令送到N個邏輯執行單元,也就是說GPU只需要相對於傳統的處理器1/N的指令帶寬。

                 


四、GPU 線程

        看下面一段代碼,就是內核函數:

        

        __global__ 前綴是告訴編譯器在編譯這個函數的時候生成是GPU代碼而不是CPU代碼,並且這段GPU代碼在CPU上是全局可見的。

        CPU和GPU有各自獨立的內存空間,因此在GPU代碼中,不可以直接訪問CPU端的參數,反過來在CPU代碼中,也不可以直接訪問GPU端的參數。

        線程的信息是由一個結構體存儲的。如,threadIdx.x

         

         線程0中的thread_idx值爲0,線程1的爲1,依次類推,線程127中的thread_idx值爲127。每個線程都進行了兩次讀內存操作,一次乘法操作,一次存儲操作,然後結束。我們注意到,每個線程執行的代碼是一樣的,但數據卻不相同。這就是CUDA的核心---SPMD模型。


五、硬件執行線程

       GPU 的每個線程組被送到SM 中,然後N 個SP 開始執行代碼。所有的線程不是立即發生的。實際上,當從存儲子系統取得所需要的數後,已經過去了400-600個GPU時鐘週期。這一期間,這一組中的N個線程都被掛起。

       實際上,線程都是以每32個一組,當所有32個線程都在等待諸如內存讀取這樣的操作時,他們就會被掛起。這些線程組叫線程束(32個線程)或半個線程束(16個線程)。

       因此,我們可以將這128個線程分成4組,每組32個線程。首先讓所有的線程提取線程標號,計算得到數組地址,然後發出一條內存獲取的指令。接着下一條指令是做乘法,但這必須是在從內存讀取數據的時間很長,因此線程會掛起。當這組中的32個線程全部掛起,硬件就會切換到另外一個線程束。

               

        在圖5-5 中我們可以看到,當線程束0 由於內存讀取操作而掛起時,線程束1 就成爲了整治執行的線程束。GPU一直以此種方式運行直到所有的線程束到成爲掛起狀態(如圖5-6所示)。

        當連續的線程發出讀取內存的指令時,讀取操作會被合併或組合在一起執行。由於硬件在管理請求時會產生一定的開銷,因此這樣做將減少延遲(響應請求的時間)。由於合併,內存讀取會返回數組線程所需要的數據,一般可以返回整個線程所需的數據。

        在完成內存讀取之後,這些線程將再次置成就緒狀態,當再次遇到阻塞操作時,例如另一個線程進行內存讀取,GPU可能將這個線程束用作另一塊內存讀取。

        當所有的線程束(每組32個線程)都在等待內存讀取操作完成時,GPU將會閒置。但到達某個時間點之後,GPU將從存儲子系統返回一個內存塊序列,並且這個序列的順序通常與發出請求的順序是一致的。

        假設數組下標爲0-31 的元素在同一時間返回,線程束0進入就緒隊列。如果當前沒有如何線程束正在執行,則線程束0將自動進入執行狀態(如圖5-4)。漸漸地其他所有掛起的線程束也都完成了內存讀取操作,緊接着它們也會返回到就緒隊列。

       一旦線程束0 的乘法指令執行完畢,它就只剩下一條指令需要執行,即將計算得到的結果寫入相同下標的數組a 中。由於再沒有依賴該操作的其他指令,線程束0 全部執行完畢然後消失。其他線程束也像這樣,最終發出一條寫數據的請求,完成之後便消亡。當所有的線程束都消亡後,整個內核函數也就結束了,最終將控制返回到CPU端。

                    

                    

                    







  



















發佈了119 篇原創文章 · 獲贊 55 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章