【CUDA入门笔记】CUDA内核与线程配置

1.CUDA核函数

在GPU上调用的函数成为CUDA核函数(Kernel function),核函数会被GPU上的多个线程执行。每个线程都会执行核函数里的代码,当然由于线程编号的不同,执行的代码路径可能会有所不同。

(1)函数的最前面是声明标识符__global__,该标识符表示这个函数可以在GPU上执行。需要指出的是尽管是在GPU上执行,但是仍然是由CPU端发起调用的

(2)核函数调用时需要用<<<...>>>符号来指定线程配置

(3)在核函数内部可以调用CUDA内置变量,比如threadIdx,blockDim等

(4)核函数相对于CPU代码是异步的,也就是控制会在核函数执行完成之前就返回,这样CPU就可以不用等待核函数的完成而继续执行后面的CPU代码

(5)核函数内部只能访问device内存。因为核函数是执行在设备端,所以只能访问设备端内存。

(6)必须返回void类型。我们知道核函数是由CPU端发起的并执行在GPU上的函数。在核函数内部的数据均是位于GPU上的,假设核函数有返回值,那么返回值是位于GPU上的数据,CPU去直接接收这个数据是不被允许的。所以,核函数没有返回值。

(7)核函数不支持可变参数;核函数不支持静态变量;
核函数不支持函数指针;

(8)在CUDA编程中,除了__global__外,常用的标识符还有:__device__。有标识符__device__的函数只能在GPU段执行,只能在GPU段调用,比如可以在__global__以及__device__函数中调用,__global__与__device__不能同时使用。

(9)另外一个常用的标识符是__host__:只能在host端执行;只能在host端调用。单独使用__host__的情况时,该函数与普通的CPU函数的性质及使用方法没有任何差别。那既然这样为什么还要引入这个标识符呢?我们可以想象有这样一种情况,一个函数我们希望它既可以在CPU上调用也可以在GPU上调用,那么我们这样声明这个函数:host device funForCPUandGPU(args), 则这个函数既可以在CPU上执行也可以在GPU上执行。

2.线程配置

在Host端核函数的调用方式为:

kernel<<<Dg, Db, Ns, S>>>(param list);

其中,

  • Dg:int型或者dim3类型(x,y,z),用于定义一个Grid中Block是如何组织的,如果是int型,则表示一维组织结构
  • Db:int型或者dim3类型(x,y,z),用于定义一个Block中Thread是如何组织的,如果是int型,则表示一维组织结构
  • Ns:size_t类型,可缺省,默认为0; 用于设置每个block除了静态分配的共享内存外,最多能动态分配的共享内存大小,单位为byte。 0表示不需要动态分配。
  • S:cudaStream_t类型,可缺省,默认为0。 表示该核函数位于哪个流。

当使用dim3类型时,比如:

dim3 grid(3,2,1), block(4,3,1);

 表示一个Grid中有3x2x1=6个Block,在(x,y,z)三个方向上的排布方式分别是3、2、1;一个Block中有4x3x1=12个Thread,在(x,y,z)三个方向上的排布方式分别是4、3、1。

当使用int类型时,表示一维排布,比如:

kernel_name<<<5,8>>>(...);

表示一个Grid中有5个Block,在(x,y,z)三个方向上的排布方式分别是5、1、1;一个Block中有8个Thread,在(x,y,z)三个方向上的排布方式分别是8、1、1。
 

在CUDA上可以使用内置变量来获取Thread ID和Block ID:

  • threadIdx.[x, y, z]表示Block内Thread的编号
  • blockIdx.[x, y, z]表示Gird内Block的编号
  • blockDim.[x, y, z]表示Block的维度,也就是Block中每个方向上的Thread的数目
  • gridDim.[x, y, z]表示Gird的维度,也就是Grid中每个方向上Block的数目
     

具体看一下不同维度下,threadId的计算方式:

#一维Grid 一维Block
kernel_name<<<4, 8>>>(...)
int threadId = blockIdx.x * blockDim.x + threadIdx.x = 2 * 4 + 1 = 9


#二维Grid 二维Block

dim grid(4,1,1), block(2,2,1);
kernel_name<<<grid, block>>>(...)

int blockId = blockIdx.x + blockId.y * gridDim.x;
int threadId = blockId * (blockDim.x * blockDim.y) + (threadIdx.y *blockDim.x) + threadIdx.x;

#三维Grid 三维Block

int blockId = blockIdx.x + blockIdx.y * gridDim.x + gridDim.x * gridDim.y * blockIdx.z;
int threadIc = blockId * (blockDim.x * blockDim.y * blockDim.z) 
                       + (threadIdx.z * (blockDim.x * blockDim.y)) 
                       + (threadIdx.y * blockDim.x) + threadIdx.x;   

当Grid与block维度不同时:

#一维Grid 二维Block

blockId = blockIdx.x 
threadId = blockIdx.x * blockDim.x * blockDim.y + threadIdx.y * blockDim.x + threadIdx.x


#一维Grid 三维Block
blockId = blockIdx.x 
threadId = blockIdx.x * blockDim.x * blockDim.y * blockDim.z 
                      + threadIdx.z * blockDim.y * blockDim.x 
                      + threadIdx.y * blockDim.x + threadIdx.x

#二维Grid 一维Block

int blockId = blockIdx.y * gridDim.x + blockIdx.x;  
int threadId = blockId * blockDim.x + threadIdx.x;

#二维Grid 三维Block
int blockId = blockIdx.x + blockIdx.y * gridDim.x;  
int threadId = blockId * (blockDim.x * blockDim.y * blockDim.z)  
                       + (threadIdx.z * (blockDim.x * blockDim.y))  
                       + (threadIdx.y * blockDim.x) + threadIdx.x;  

如果要计算在调用kernel函数时, 对于多维Grid中每个线程在全局中的索引(x, y),则可以表示成如下形式:

dim3 gridDim = (blocksPerGrid, blocksPerGrid);
dim3 blockDim = (threadsPerBlock, threadsPerBlock);

int x= blockIdx.x + blockId.x * gridDim.x;
int y= blockIdx.y + blockId.y * gridDim.y;


猜你喜欢

转载自blog.csdn.net/qq_34106574/article/details/128392585