CUDA編程學習筆記

CUDA:Compute Unified Device Architecture,是由NVIDIA所推出的一種集成技術(shù),允許使用標準C來進行GPU代碼編程,最終轉(zhuǎn)為PTX匯編代碼。


CPU與GPU

GPU可以看作是CPU的協(xié)助處理器,使用GPU實際指的是基于CPU+GPU的異構(gòu)計算架構(gòu)。通過PCle總線連接,CPU端成為Host端,GPU端稱為Device端。

基于CPU+GPU的異構(gòu)計算架構(gòu)

GPU適合數(shù)據(jù)并行的計算密集型任務(wù),如大型矩陣運算,而CPU的運算核心較少,但是其可以實現(xiàn)復雜的邏輯運算,因此其適合控制密集型任務(wù)。此外,CPU上的線程是重量級的,上下文切換開銷大,但是GPU由于存在很多核心,其線程是輕量級的。因此,基于CPU+GPU的異構(gòu)計算平臺可以優(yōu)勢互補,CPU負責處理邏輯復雜的串行程序,而GPU重點處理數(shù)據(jù)密集型的并行計算程序。


CUDA程序執(zhí)行流程

1、分配host內(nèi)存,并進行數(shù)據(jù)初始化;

2、分配device內(nèi)存,并從host將數(shù)據(jù)拷貝到device上;

3、調(diào)用CUDA kernel在device上完成指定的運算;(kernel是在device上線程中并行執(zhí)行的函數(shù),核函數(shù)用__global__符號聲明,在調(diào)用時需要用<<<grid, block>>>來指定kernel要執(zhí)行的線程數(shù)量,在CUDA中,每一個線程都要執(zhí)行核函數(shù),并且每個線程會分配一個唯一的線程號thread ID,這個ID值可以通過核函數(shù)的內(nèi)置變量threadIdx來獲得。)

4、將device上的運算結(jié)果拷貝到host上;

5、釋放device和host上分配的內(nèi)存。

CUDA函數(shù)類型限定詞

? ? ? ? 1)__global__:在device上執(zhí)行,從host中調(diào)用(一些特定的GPU也可以從device上調(diào)用),返回類型必須是void,不支持可變參數(shù)參數(shù),不能成為類成員函數(shù)。注意用__global__定義的kernel是異步的,這意味著host不會等待kernel執(zhí)行完就執(zhí)行下一步。

? ? ? ? 2)__device__:在device上執(zhí)行,單僅可以從device中調(diào)用,不可以和__global__同時用。

? ? ? ? 3)__host__:在host上執(zhí)行,僅可以從host上調(diào)用,一般省略不寫,不可以和__global__同時用,但可和__device__,此時函數(shù)會在device和host都編譯。

Kernel線程結(jié)構(gòu)

kernel在device上執(zhí)行時實際上是啟動很多線程,一個kernel所啟動的所有線程稱為一個網(wǎng)格(grid),同一個網(wǎng)格上的線程共享相同的全局內(nèi)存空間,grid是線程結(jié)構(gòu)的第一層次,而網(wǎng)格又可以分為很多線程塊(block),一個線程塊里面包含很多線程,這是第二個層次。grid和block都是定義為dim3類型的變量,dim3可以看成是包含三個無符號整數(shù)(x,y,z)成員的結(jié)構(gòu)體變量,在定義時,缺省值初始化為1。因此grid和block可以靈活地定義為1-dim,2-dim以及3-dim結(jié)構(gòu),對于圖中結(jié)構(gòu)(主要水平方向為x軸),定義的grid和block如下所示,kernel在調(diào)用時也必須通過執(zhí)行配置<<<grid, block>>>來指定kernel所使用的線程數(shù)及結(jié)構(gòu)。

Kernel線程結(jié)構(gòu)

一個線程需要兩個內(nèi)置的坐標變量(blockIdx,threadIdx)來唯一標識,它們都是dim3類型變量,其中blockIdx指明線程所在grid中的位置,而threaIdx指明線程所在block中的位置。以Thread(1, 1)為例:

? ? ? ? threadIdx.x = 1? ? ? ? ? ?threadIdx.y = 1

????????blockIdx.x = 1? ? ? ? ? ? ?blockIdx.y =1

測試GPU硬件配置

查看GPU配置

CUDA內(nèi)存模型

每個線程有自己的私有本地內(nèi)存(Local Memory),而每個線程塊有包含共享內(nèi)存(Shared Memory),可以被線程塊中所有線程共享,其生命周期與線程塊一致。此外,所有的線程都可以訪問全局內(nèi)存(Global Memory)。還可以訪問一些只讀內(nèi)存塊:常量內(nèi)存(Constant Memory)和紋理內(nèi)存(Texture Memory)。

分配托管內(nèi)存?

分配托管內(nèi)存
使用統(tǒng)一內(nèi)存更簡潔了,由于托管內(nèi)存自動進行數(shù)據(jù)傳輸,這里要用cudaDeviceSynchronize()函數(shù)保證device和host同步,這樣后面才可以正確訪問kernel計算的結(jié)果。

實例:矩陣加法

利用上圖2-dim結(jié)構(gòu)實現(xiàn)兩個矩陣的加法,每個線程負責處理每個位置的兩個元素相加,代碼如下所示。線程塊大小為(16, 16),然后將N*N大小的矩陣均分為不同的線程塊來執(zhí)行加法運算。

矩陣加法

實例:矩陣乘法

設(shè)輸入矩陣為?

?和?

?,要得到?

?。實現(xiàn)思路是每個線程計算?

?的一個元素值?

?,對于矩陣運算,應(yīng)該選用grid和block為2-D的。首先定義矩陣的結(jié)構(gòu)體:

nvprof工具

通過命令nvprof cuda9.exe可以得到kernel的運行情況,


參考資料

CUDA編程入門極簡教程 - 知乎

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容