一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

從0開(kāi)始學(xué)習(xí)《GPU高性能運(yùn)算之CUDA》

 雪柳花明 2016-11-18

5 GPU也不允許偏心

并行的事情多了,我們作為GPU的指令分配者,不能偏心了——給甲做的事情多,而乙沒(méi)事做,個(gè)么甲肯定不爽的來(lái)。所以,在GPU中,叫做線程網(wǎng)絡(luò)的分配。首先還是來(lái)看下GPU的線程網(wǎng)絡(luò)吧,圖2

 

線程網(wǎng)絡(luò)

我們將具體點(diǎn)的,在主機(jī)函數(shù)中如果我們分配的是這樣的一個(gè)東西:

dim3 blocks(32,32);

dim3 threads(16,16);

dim3是神馬?dim3是一個(gè)內(nèi)置的結(jié)構(gòu)體,和Linux下定義的線程結(jié)構(gòu)體是個(gè)類似的意義的東西,dim3結(jié)構(gòu)變量有x,y,z,表示3維的維度。不理解沒(méi)關(guān)系,慢慢看。

kernelfun<<<blocks, threads>>>();

我們調(diào)用kernelfun這個(gè)內(nèi)核函數(shù),將blocksthreads傳到<<<,>>>里去,這句話可牛逼大了——相當(dāng)于發(fā)號(hào)施令,命令那些線程去干活。這里使用了32*32 * 16*16個(gè)線程來(lái)干活。你看明白了嗎?blocks表示用了二維的32*32個(gè)block組,而每個(gè)block中又用了16*16的二維的thread組。好吧,我們這個(gè)施令動(dòng)用了262144個(gè)線程!我們先不管GPU內(nèi)部是如何調(diào)度這些線程的,反正我們這一句話就是用了這么多線程。

那我們的內(nèi)核函數(shù)kernelfun()如何知道自己執(zhí)行的是哪個(gè)線程?這就是線程網(wǎng)絡(luò)的特點(diǎn)啦,為什么叫網(wǎng)絡(luò),是有講究的,網(wǎng)絡(luò)就可以定格到網(wǎng)點(diǎn):

比如int tid = threadId.x + blockId.x * 16

這里有一個(gè)講究,block是有維度的,一維、二維、三維。

對(duì)于一維的block,tid = threadId.x

對(duì)于(Dx,Dy)二維的block,tid = threadId.x + Dx*threadId.y

對(duì)于(Dx,DyDz)三維的block,tid = threadId.x + Dx*threadId.y + Dz*Dy*threadId.z

我習(xí)慣的用這樣的模式去分配,比較通用:

dim3 dimGrid();

dim3 dimBlock();

kerneladd<<<dimGrid, dimBlock>>>();

這可是萬(wàn)金油啊,你需要做的事情是填充dimGriddimBlock的結(jié)構(gòu)體構(gòu)造函數(shù)變量,比如,dimGrid(16, 16)表示用了16*16的二維的block線程塊。

0,0)(0,1)(0,2)……(0,15

1,0)(1,1)(1,2)……(1,15

2,0)(2,1)(2,2)……(2,15

……

15,0)(15,1)(15,2)……(15,15

(,)是(dimGrid.x, dimGrid.y)的網(wǎng)格編號(hào)。

我們這么理解吧,現(xiàn)在又一群人,我們分成16*16個(gè)小組(block),排列好,比如第3行第4列就指的是(2,3)這個(gè)小組。

dimBlock16,16)表示每個(gè)小組有16*16個(gè)成員,如果你想點(diǎn)名第3行第4列這個(gè)小組的里面的第3行第4列那個(gè)同學(xué),那么,你就是在(2,3)這個(gè)block中選擇了(2,3)這個(gè)線程。這樣應(yīng)該有那么一點(diǎn)可以理解進(jìn)去的意思了吧?不理解透徹么什么關(guān)系,這個(gè)東西本來(lái)就是cuda中最讓我糾結(jié)的事情。我們且不管如何分配線程,能達(dá)到最優(yōu)化,我們的目標(biāo)是先讓GPU正確地跑起來(lái),計(jì)算出結(jié)果即可,管他高效不高效,管他環(huán)保不環(huán)保。

嘮叨了這么多,下面我們用一個(gè)最能說(shuō)明問(wèn)題的例子來(lái)進(jìn)一步理解線程網(wǎng)絡(luò)分配機(jī)制來(lái)了解線程網(wǎng)絡(luò)的使用。

一維網(wǎng)絡(luò)線程

egint arr[1000],對(duì)每個(gè)數(shù)組元素進(jìn)行加1操作。

idea:我們最直接的想法,是調(diào)度1000個(gè)線程去干這件事情。

first pro:我想用一個(gè)小組的1000個(gè)人員去干活。這里會(huì)存在這樣一個(gè)問(wèn)題——一個(gè)小組是不是有這么多人員呢?是的,這個(gè)事情你必須了解,連自己組內(nèi)多少人都不知道,你也不配作指揮官呀。對(duì)的,這個(gè)參數(shù)叫做maxThreadsPerBlock,如何取得呢?

好吧,cuda定義了一個(gè)結(jié)構(gòu)體cudaDeviceProp,里面存入了一系列的結(jié)構(gòu)體變量作為GPU的參數(shù),出了maxThreadsPerBlock,還有很多信息哦,我們用到了再說(shuō)。

maxThreadsPerBlock這個(gè)參數(shù)值是隨著GPU級(jí)別有遞增的,早起的顯卡可能512個(gè)線程,我的GT520可以跑1024個(gè)線程,辦公室的GTX650ti2G可以跑1536個(gè),無(wú)可非議,當(dāng)然多多益善。一開(kāi)始,我在想,是不是程序?qū)⒚總€(gè)block開(kāi)的線程開(kāi)滿是最好的呢?這個(gè)問(wèn)題留在以后在說(shuō),一口吃不成胖子啦。

好吧,我們的數(shù)組元素1000個(gè),是可以在一個(gè)block中干完的。

內(nèi)核函數(shù):

#define N 1000

__gloabl__ void kerneladd(int *dev_arr)

{

int tid = threadId.x;

if (tid < 1000)

dev_arr[tid] ++;

}

int main()

{

int *arr, *dev_arr;// 習(xí)慣的我喜歡在內(nèi)核函數(shù)參數(shù)變量前加個(gè)dev_作為標(biāo)示

// 開(kāi)辟主機(jī)內(nèi)存,arr = (int*)malloc(N*sizeof(int));

// 開(kāi)辟設(shè)備內(nèi)存

// 主機(jī)拷貝到設(shè)備

kerneladd<<<1, N>>>(dev_arr);

// 設(shè)備拷貝到主機(jī)

// 打印

// 釋放設(shè)備內(nèi)存

// 釋放主機(jī)內(nèi)存

return 0;

}

 

呀,原來(lái)這么簡(jiǎn)單,個(gè)么CUDA也忒簡(jiǎn)單了哇!這中想法是好的,給自己提高信心,但是這種想法多了是不好的,因?yàn)楹竺娴膯?wèn)題多了去了。

盆友說(shuō),1000個(gè)元素,還不如CPU來(lái)的快,對(duì)的,很多情況下,數(shù)據(jù)量并行度不是特別大的情況下,可能CPU來(lái)的更快一些,比較設(shè)備與主機(jī)之間互相調(diào)度操作,是會(huì)有額外開(kāi)銷的。有人就問(wèn)了,一個(gè)10000個(gè)元素的數(shù)組是不是上面提供的idea就解決不了啦?對(duì),一個(gè)block人都沒(méi)怎么多,如何完成!這個(gè)情況下有兩條路可以選擇——

第一,我就用一個(gè)組的1000人來(lái)干活話,每個(gè)人讓他干10個(gè)元素好了。

這個(gè)解決方案,我們需要修改的是內(nèi)核函數(shù):

__global__ void kernelarr(int *dev_arr)

{

int tid = threadId.x;

if(tid < 1000) // 只用0~999號(hào)線程

{ //每個(gè)線程處理10個(gè)元素,比如0號(hào)線程處理0、1001、2001、……9001

for(int i = tid; i<N; i=i+1000)

{

dev_arr[tid] ++;

}

}

}

第二,我多用幾個(gè)組來(lái)干這件事情,比如我用10個(gè)組,每個(gè)組用1000人。

這個(gè)解決方案就稍微復(fù)雜了一點(diǎn),注意只是一點(diǎn)點(diǎn)哦~因?yàn)?,組內(nèi)部怎么干活和最原始的做法是一樣的,不同之處是,我們調(diào)遣了10個(gè)組去干這件事情。

首先我們來(lái)修改我們的主機(jī)函數(shù):

int main()

{

……

kerneladd<<<10, 1000>>>(dev_arr);//我們調(diào)遣了10個(gè)組,每個(gè)組用了1000

……

}

盆友要問(wèn)了,10個(gè)組每個(gè)組1000人,你怎么點(diǎn)兵呢?很簡(jiǎn)單啊,第1組第3個(gè)線程出列,第9組第9個(gè)線程出列。每個(gè)人用組號(hào)和組內(nèi)的編號(hào)定了位置。在線程網(wǎng)絡(luò)中,blockId.xthreadId.x就是對(duì)應(yīng)的組號(hào)和組內(nèi)編號(hào)啦,我必須要這里開(kāi)始形象點(diǎn)表示這個(gè)對(duì)應(yīng)關(guān)系,如果這個(gè)對(duì)應(yīng)關(guān)系是這樣子的[blockId.x,threadId.x],那么我們的數(shù)組arr[10000]可以這樣分配給這10個(gè)組去干活:

(0,0)——arr[0],(0,1)——arr[1],……(0,999)——arr[999]

(1,0)——arr[0+1*1000],(1,1)——arr[1+1*1000],……(1,999)——arr[999+1*1000]

……

(9,0)——arr[0+9*1000],(9,1)——arr[1+9*1000],……(9,999)——arr[999+9*1000]

是不是很有規(guī)律呢?對(duì)的,用blockId.xthreadId.x可以很好的知道哪個(gè)線程干哪個(gè)元素,這個(gè)元素的下表就是threadId.x + 1000*blockId.x。

這里我想說(shuō)的是,如果我們哪天糊涂了,畫一畫這個(gè)對(duì)應(yīng)關(guān)系的表,也許,就更加清楚的知道我們分配的線程對(duì)應(yīng)的處理那些東西啦。 

一維線程網(wǎng)絡(luò),就先學(xué)這么多了。

二維網(wǎng)絡(luò)線程

eg2int arr[32][16]二維的數(shù)組自增1。

第一個(gè)念頭,開(kāi)個(gè)32*16個(gè)線程好了哇,萬(wàn)事大吉!好吧。但是,朕現(xiàn)在想用二維線程網(wǎng)絡(luò)來(lái)解決,因?yàn)殡抻X(jué)得一個(gè)二維的網(wǎng)絡(luò)去映射一個(gè)二維的數(shù)組,朕看的更加明了,看不清楚自己的士兵,如何帶兵打仗!

我還是畫個(gè)映射關(guān)系:

一個(gè)block中,現(xiàn)在是一個(gè)二維的thread網(wǎng)絡(luò),如果我用了16*16個(gè)線程。

(0,0),(0,1),……(0,15)

(1,0)(1,1),……(1,15)

……

(15,0),(15,1),……(15,15)

呀,現(xiàn)在一個(gè)組內(nèi)的人稱呼變了嘛,一維網(wǎng)絡(luò)中,你走到一個(gè)小組里,叫3號(hào)出列,就出來(lái)一個(gè),你現(xiàn)在只是叫3號(hào),沒(méi)人會(huì)出來(lái)!這個(gè)場(chǎng)景是這樣的,現(xiàn)在你班上有兩個(gè)人同名的人,你只叫名,他們不知道叫誰(shuí),你必須叫完整點(diǎn),把他們的姓也叫出來(lái)。所以,二維網(wǎng)絡(luò)中的(0,3)就是原來(lái)一維網(wǎng)絡(luò)中的3,二維中的(i,j)就是一維中的(j+i*16)。不管怎么樣,一個(gè)block里面能處理的線程數(shù)量總和還是不變的。

一個(gè)grid中,block也可以是二維的,一個(gè)block中已經(jīng)用了16*16thread了,那我們一共就32*16個(gè)元素,我們用2個(gè)block就行了。

先給出一個(gè)代碼清單吧,程序員都喜歡看代碼,這段代碼是我抄襲的。第一次這么完整的放上代碼,因?yàn)槲矣X(jué)得這個(gè)代碼可以讓我說(shuō)明我想說(shuō)的幾個(gè)問(wèn)題:

第一,二維數(shù)組和二維指針的聯(lián)系。

第二,二維線程網(wǎng)絡(luò)。

第三,cuda的一些內(nèi)存操作,和返回值的判斷。

 

 #include <stdio.h> 

 #include <stdlib.h> 

 #include <cuda_runtime.h> 

 

 #define ROWS 32 

 #define COLS 16 

 #define CHECK(res) if(res!=cudaSuccess){exit(-1);} 

 __global__ void Kerneltest(int **da, unsigned int rows, unsigned int cols) 

 { 

 unsigned int row = blockDim.y*blockIdx.y + threadIdx.y; 

 unsigned int col = blockDim.x*blockIdx.x + threadIdx.x; 

 if (row < rows && col < cols) 

 { 

 da[row][col] = row*cols + col; 

 } 

 } 

 

 int main(int argc, char **argv) 

 { 

 int **da = NULL; 

 int **ha = NULL; 

 int *dc = NULL; 

 int *hc = NULL; 

 cudaError_t res; 

 int r, c; 

 bool is_right=true; 

 

 res = cudaMalloc((void**)(&da), ROWS*sizeof(int*));CHECK(res) 

 res = cudaMalloc((void**)(&dc), ROWS*COLS*sizeof(int));CHECK(res) 

 ha = (int**)malloc(ROWS*sizeof(int*)); 

 hc = (int*)malloc(ROWS*COLS*sizeof(int)); 

 

 for (r = 0; r < ROWS; r++) 

 { 

 ha[r] = dc + r*COLS; 

 } 

 res = cudaMemcpy((void*)(da), (void*)(ha), ROWS*sizeof(int*), cudaMemcpyHostToDevice);CHECK(res) 

 dim3 dimBlock(16,16); 

 dim3 dimGrid((COLS+dimBlock.x-1)/(dimBlock.x), (ROWS+dimBlock.y-1)/(dimBlock.y)); 

 Kerneltest<<<dimGrid, dimBlock>>>(da, ROWS, COLS); 

 res = cudaMemcpy((void*)(hc), (void*)(dc), ROWS*COLS*sizeof(int), cudaMemcpyDeviceToHost);CHECK(res) 

 

 for (r = 0; r < ROWS; r++) 

 { 

 for (c = 0; c < COLS; c++) 

 { 

 printf("%4d ", hc[r*COLS+c]); 

 if (hc[r*COLS+c] != (r*COLS+c)) 

 { 

 is_right = false; 

 } 

 } 

 printf("\n"); 

 } 

 printf("the result is %s!\n", is_right? "right":"false"); 

 cudaFree((void*)da); 

 cudaFree((void*)dc); 

 free(ha); 

 free(hc); 

 getchar(); 

 return 0; 

 } 

 

簡(jiǎn)要的來(lái)學(xué)習(xí)一下二維網(wǎng)絡(luò)這個(gè)知識(shí)點(diǎn),

dim3 dimBlock(16,16); 

//定義block內(nèi)的thread二維網(wǎng)絡(luò)為16*16

dim3 dimGrid((COLS+dimBlock.x-1)/(dimBlock.x), (ROWS+dimBlock.y-1)/(dimBlock.y)); 

//定義grid內(nèi)的block二維網(wǎng)絡(luò)為1*2

unsigned int row = blockDim.y*blockIdx.y + threadIdx.y; 

//二維數(shù)組中的行號(hào)

unsigned int col = blockDim.x*blockIdx.x + threadIdx.x; 

//二維線程中的列號(hào)

 

三維網(wǎng)絡(luò)線程

dim3定義了三維的結(jié)構(gòu),但是,貌似二維之內(nèi)就能處理很多事情啦,所以,我放棄學(xué)習(xí)三維。網(wǎng)上看到的不支持三維網(wǎng)絡(luò)是什么意思呢?先放一放。

給自己充充電

同一塊顯卡,不管你是二維和三維或一維,其計(jì)算能力是固定的。比如一個(gè)block能處理1024個(gè)線程,那么,一維和二維線程網(wǎng)絡(luò)是不是處理的線程數(shù)一樣呢?

 

回答此問(wèn)題,先給出網(wǎng)絡(luò)配置的參數(shù)形式——<<<Dg,Db,Ns,S>>>,各個(gè)參數(shù)含義如下:

Dg:定義整個(gè)grid的維度,類型Dim3,但是實(shí)際上目前顯卡支持兩個(gè)維度,所以,dim3<<Dg.x, Dg.y, 1>>>z維度默認(rèn)只能為1,上面顯示出這個(gè)最大有65536*65536*1,每行有65536個(gè)block,每列有65536個(gè)block,整個(gè)grid中一共有65536*65536*1個(gè)block。

Db:定義了每個(gè)block的維度,類型Dim3,比如512*512*64,這個(gè)可以定義3維尺寸,但是,這個(gè)地方是有講究了,三個(gè)維度的積是有上限的,對(duì)于計(jì)算能力1.0、1.1GPU,這個(gè)值不能大于768,對(duì)于1.2、1.3的不能大于1024,對(duì)于我們?cè)囈辉嚨倪@塊級(jí)別高點(diǎn)的,不能大于1536。這個(gè)值可以獲取哦——maxThreadsPerBlock

Ns:這個(gè)是可選參數(shù),設(shè)定最多能動(dòng)態(tài)分配的共享內(nèi)存大小,比如16k,單不需要是,這個(gè)值可以省略或?qū)?/span>0。

S也是可選參數(shù),表示流號(hào),默認(rèn)為0。流這個(gè)概念我們這里不說(shuō)。

接著,我想解決幾個(gè)你肯定想問(wèn)的兩個(gè)問(wèn)題,因?yàn)槲铱春芏嗳讼胛疫@樣的問(wèn)這個(gè)問(wèn)題:

1 block內(nèi)的thread我們是都飽和使用嗎?

答:不要,一般來(lái)說(shuō),我們開(kāi)128256個(gè)線程,二維的話就是16*16。

2 grid內(nèi)一般用幾個(gè)block呢?

答:牛人告訴我,一般來(lái)說(shuō)是你的流處理器的4倍以上,這樣效率最高。

回答這兩個(gè)問(wèn)題的解釋,我想抄襲牛人的一段解釋,解釋的好的東西就要推廣呀:

GPU的計(jì)算核心是以一定數(shù)量的Streaming Processor(SP)組成的處理器陣列,NV稱之為Texture Processing Clusters(TPC),每個(gè)TPC中又包含一定數(shù)量的Streaming Multi-Processor(SM),每個(gè)SM包含8個(gè)SP。SP的主要結(jié)構(gòu)為一個(gè)ALU(邏輯運(yùn)算單元),一個(gè)FPU(浮點(diǎn)運(yùn)算單元)以及一個(gè)Register File(寄存器堆)。SM內(nèi)包含有一個(gè)Instruction Unit、一個(gè)Constant Memory、一個(gè)Texture Memory,8192個(gè)Register、一個(gè)16KBShare Memory、8個(gè)Stream Processor(SP)和兩個(gè)Special Function UnitsSFU)。(GeForce9300M GS只擁有1個(gè)SMThreadCUDA模型中最基本的運(yùn)行單元,執(zhí)行最基本的程序指令。Block是一組協(xié)作Thread,Block內(nèi)部允許共享存儲(chǔ),每個(gè)Block最多包含512個(gè)Thread。Grid是一組Block,共享全局內(nèi)存。Kernel是在GPU上執(zhí)行的核心程序,每一個(gè)Grid對(duì)應(yīng)一個(gè)Kernel任務(wù)。 在程序運(yùn)行的時(shí)候,實(shí)際上每32個(gè)Thread組成一個(gè)Warp,每個(gè) warp 塊都包含連續(xù)的線程,遞增線程 ID 。WarpMP的基本調(diào)度單位,每次運(yùn)行的時(shí)候,由于MP數(shù)量不同,所以一個(gè)Block內(nèi)的所有Thread不一定全部同時(shí)運(yùn)行,但是每個(gè)Warp內(nèi)的所有Thread一定同時(shí)運(yùn)行。因此,我們?cè)诙xBlock Size的時(shí)候應(yīng)使其為Warp Size的整數(shù)倍,也就是Block Size應(yīng)為32的整數(shù)倍。理論上Thread越多,就越能彌補(bǔ)單個(gè)Thread讀取數(shù)據(jù)的latency ,但是當(dāng)Thread越多,每個(gè)Thread可用的寄存器也就越少,嚴(yán)重的時(shí)候甚至能造成Kernel無(wú)法啟動(dòng)。因此每個(gè)Block最少應(yīng)包含64個(gè)Thread,一般選擇128或者256,具體視MP數(shù)目而定。一個(gè)MP最多可以同時(shí)運(yùn)行768個(gè)Thread,但每個(gè)MP最多包含8個(gè)Block,因此要保持100%利用率,Block數(shù)目與其Size有如下幾種設(shè)定方式:? 2 blocks x 384 threads ? 3 blocks x 256 threads ? 4 blocks x 192 threads ? 6 blocks x 128 threads ? 8 blocks x 96 threads 

這些電很重要啊,必須要充!不然,我就很難理解為什么網(wǎng)絡(luò)線程如何分配的。

1

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    亚洲最大福利在线观看| 国产精品一区二区三区欧美 | 日韩精品免费一区二区三区| 超碰在线播放国产精品| 国产一区二区三区av在线| 人人妻在人人看人人澡| 粉嫩内射av一区二区| 国产精品亚洲精品亚洲| 神马午夜福利一区二区| 国产成人午夜福利片片| 激情综合网俺也狠狠地| 好吊妞视频只有这里有精品| 国产av天堂一区二区三区粉嫩| 欧美日韩国产另类一区二区| 久久精品国产在热久久| 一区二区三区欧美高清| 人妻一区二区三区多毛女| 东京热加勒比一区二区三区| 国产亚洲欧美日韩国亚语| 日本熟妇五十一区二区三区 | 少妇人妻一级片一区二区三区| 五月的丁香婷婷综合网| 最近的中文字幕一区二区| 色丁香之五月婷婷开心| 国产一区二区不卡在线播放| 亚洲精品美女三级完整版视频| 国产性色精品福利在线观看| 日本在线高清精品人妻| 白白操白白在线免费观看| 久久亚洲国产视频三级黄| 欧美精品日韩精品一区| 欧美日韩一区二区午夜| 午夜久久久精品国产精品| 亚洲欧美日韩中文字幕二欧美| 亚洲男人天堂成人在线视频| 婷婷色国产精品视频一区| 成人午夜视频精品一区| 国产亚洲精品久久久优势| 中文字幕在线区中文色| 午夜资源在线观看免费高清| 国产精品一区二区三区激情|