相关阅读
CUDA Chttps://blog.csdn.net/weixin_45791458/category_12530616.html?spm=1001.2014.3001.5482
第一百篇博客,写点不一样的。
当核函数在主机端被调用时,它会被转移到设备端执行,此时设备会根据核函数的调用格式产生对应的线程(thread),并且每个线程都执行核函数指定的语句。
CUDA提供了线程的层次结构以便于组织线程,自顶而下可以分为线程格、线程块和线程。由一个内核启动的所有线程统称为一个线程格(grid),同一线程格中的所有线程共享相同的全局内存空间。一个线程格由多个线程块(block)构成,一个线程块由包含若干线程,同一线程块内的线程可以通过以下两种方式协作,而不同线程块内线程不能协作。
- 同步
- 共享内存
线程通过下面两个核函数的预置变量来区分彼此,预置变量代表着CUDA在运行时为每一个进程都分配了这两个变量,基于这两个变量,可以将一块数据分给不同的进程处理。
- blockIdx(线程块在线程格内的索引)
- threadIdx(线程在线程块中的索引)
这两个变量是由一个名为uint3的结构定义的,这实际上就是CUDA内置的一个包含三个无符号整数的结构体,如下所示。
//这个定义在vector_types.h头文件中 struct __device_builtin__ uint3 { unsigned int x, y, z; }; typedef __device_builtin__ struct uint3 uint3;根据定义,这两个变量可以通过下面的方式访问结构的成员。
blockIdx.x //线程块索引的x分量 blockIdx.y //线程块索引的y分量 blockIdx.z //线程块索引的y分量 threadIdx.x //线程索引的x分量 threadIdx.y //线程索引的y分量 threadIdx.z //线程索引的z分量为什么这两个结构都是三个分量,因为CUDA最多支持组织三维的层次结构,即线程块在线程格中的分布最多有三个维度,而线程在线程块中的分布最多有三个维度。CUDA使用了下面两个预置变量来保存层次结构的维度大小。
- blockDim(线程块的维度大小,用线程块中的线程数来表示)
- gridDim(线程格的维度大小,用线程格中的线程块数来表示)
这两个预置变量是由一个名为dim3的结构定义的,这实际上也是CUDA内置的一个包含三个无符号整数的结构体,如下所示。
//这个定义在vector_types.h头文件中 struct __device_builtin__ dim3 { unsigned int x, y, z; #if defined(__cplusplus) #if __cplusplus >= 201103L __host__ __device__ constexpr dim3(unsigned int vx = 1, unsigned int vy = 1, unsigned int vz = 1) : x(vx), y(vy), z(vz) {} __host__ __device__ constexpr dim3(uint3 v) : x(v.x), y(v.y), z(v.z) {} __host__ __device__ constexpr operator uint3(void) const { return uint3{x, y, z}; } #else __host__ __device__ dim3(unsigned int vx = 1, unsigned int vy = 1, unsigned int vz = 1) : x(vx), y(vy), z(vz) {} __host__ __device__ dim3(uint3 v) : x(v.x), y(v.y), z(v.z) {} __host__ __device__ operator uint3(void) const { uint3 t; t.x = x; t.y = y; t.z = z; return t; } #endif #endif /* __cplusplus */ }; typedef __device_builtin__ struct dim3 dim3;根据定义,这两个变量可以通过下面的方式访问结构的成员。
blockDim.x //线程块x方向的维度大小 blockDim.y //线程块y方向的维度大小 blockDim.z //线程块z方向的维度大小 gridDim.x //线程格x方向的维度大小 gridDim.y //线程格y方向的维度大小 gridDim.z //线程格z方向的维度大小通常情况下,一个线程格拥有两个维度即,一个线程块拥有三个维度。如果维度数小于3,则多余的维度对应的Dim变量成员会被初始化为1。
需要特别说明的是,上面谈到的四个预置变量只有在核函数内部也可以说设备端才能访问到。而在主机端,为了调用核函数,可以自行定义dim3数据类型的变量,这些在主机端定义的变量在核函数内部是不可访问的。
下面的程序验证了如何使用这些预置变量以及自行定义dim3数据类型的变量。
#include <cuda_runtime.h> #include <stdio.h> __global__ void checkIndex(void) //定义核函数,显示本进程的预置变量 { printf("threadIdx:(%d, %d, %d)\n", threadIdx.x, threadIdx.y, threadIdx.z); printf("blockIdx:(%d, %d, %d)\n", blockIdx.x, blockIdx.y, blockIdx.z); printf("blockDim:(%d, %d, %d)\n", blockDim.x, blockDim.y, blockDim.z); printf("gridDim:(%d, %d, %d)\n", gridDim.x, gridDim.y, gridDim.z); } int main(int argc, char **argv) { //定义数据量 int nElem = 6; //定义了两个dim类型的变量block和grid用于核函数调用 dim3 block(3); //注意这里使用了构造函数创建结构变量 //dim3 block(3, 1, 1); 等价 dim3 grid((nElem + block.x - 1) / block.x); //dim3 grid((nElem + block.x - 1) / block.x, 1, 1);等价 //显示block和grid的分量值 printf("grid.x %d grid.y %d grid.z %d\n", grid.x, grid.y, grid.z); printf("block.x %d block.y %d block.z %d\n", block.x, block.y, block.z); //使用block和grid进行核函数调用 checkIndex<<<grid, block>>>(); //checkIndex<<<3, (nElem + block.x - 1) / block.x>>>();直接用整数调用也是可以的 //复位设备端 cudaDeviceReset(); return(0); }因为printf函数只支持Fermi架构以上的GPU架构,所以在编译时需要指定架构为sm_20或以上,如下所示(默认情况下,nvcc会产生它所支持的最低版本架构的代码)。
$nvcc -arch=sm_20 checkDimension.cu -o check $./check程序的输出如下所示。
grid.x 2 grid.y 1 grid.z 1 block.x 3 block.y 1 block.z 1 threadIdx:(0, 0, 0) threadIdx:(1, 0, 0) threadIdx:(2, 0, 0) threadIdx:(0, 0, 0) threadIdx:(1, 0, 0) threadIdx:(2, 0, 0) blockIdx:(0, 0, 0) blockIdx:(0, 0, 0) blockIdx:(0, 0, 0) blockIdx:(1, 0, 0) blockIdx:(1, 0, 0) blockIdx:(1, 0, 0) blockDim:(3, 1, 1) blockDim:(3, 1, 1) blockDim:(3, 1, 1) blockDim:(3, 1, 1) blockDim:(3, 1, 1) blockDim:(3, 1, 1) gridDim:(2, 1, 1) gridDim:(2, 1, 1) gridDim:(2, 1, 1) gridDim:(2, 1, 1) gridDim:(2, 1, 1) gridDim:(2, 1, 1)写在最后:这是我的第100篇博客,回想从写第一篇博客到现在,也只有短短10个月,但是发博客似乎已经成为了我的习惯,希望自己能一直坚持下去,努力提升自己的技术!
最后的最后:感谢我的父母和小李同学一直以来的支持与帮助!