CUDA的几个知识点

知识点1:

ncvv编译步骤:

nvcc对CUDA代码段进行编译,所有非CUDA的代码均交给通用的C编译器,windows下是C1编译器,Linux下是gcc编译器。
若想要了解nvcc的编译步骤:
1, 在cmd下输入nvcc -E x.cu > x.cup 可以得到cup后缀名的文件,这个步骤一般是由nvcc隐式执行的,这一步是对cu文件进行预处理。
(若出现错误:

nvcc fatal   : Cannot find compiler 'cl.exe' in PATH

是因为在系统变量中没有vc++这个编译器,在vs相应版本的bin文件中可以找到,并且要在path下设置这个目录,并且重新启动)
2, 预处理后,将cup文件送给cuda前端,即cudafe,通过cudafe这个exe来分离源文件,然后调用不同的编译器进行编译。cudafe被称为CUDA frontend,会被调用两次,完成两个工作:一是将主机代码与设备代码分离,生成gpu文件,二是对gpu文件进行dead code analysis,传给nvopencc。
3, 同时,在编译阶段CUDA源代码对C语言所扩展的部分将被转成regular ANSI C的源文件,也就可以由一般的C编译器进行更多的编译和连接。即是设备代码被编译成ptx(parallel thread execution)代码和/或二进制代码,host代码则以C文件形式输出,在编译时可将设备代码描述符链接到所生成的host代码,将其中的cubin对象作为全局初始化数据数组包含进来,但此时,kernel执行配置也要被转换为CUDA运行启动代码,以加载和启动编译后的kernel。在调用CUDA驱动API时,CUDA运行时系统会查看这个描述符,根据当前的GPU load一块具有合适ISA的image,然后便可单独执行ptx代码或cubin对象,而忽略nvcc编译得到的host代码。

首先nvcc是CUDA开发者使用的编译器驱动程序,可以使CUDA源代码转换成为可执行的CUDA应用程序。NVCC的功能:
1 编译
2 链接
3 用一条指令执行一个示例程序
PTX (并行线程执行)

在第一个部分,已经说明了nvcc在进行编译时的主要工作流程,以下几个变量就是nvcc自身控制各个编译阶段所需要使用的相关变量。

nvcc执行时用到的几个变量:

(1)compiler-bindir
用于指定主机编译器所在的目录,可以使用编译选项–compiler-bindir进行修改。
(2)INCLUDES
这个字符串扩展nvcc的命令选项–Xcompiler,它也定义一些附加的include目录,在实际的编译选项语法范围内,如Linux下的gcc 语法或Windows下的cl 语法。
(3)LIBRARIES
这个字符串扩展nvcc的命令选项–Xlinker,它也定义一些附加的库文件和库文件目录,在实际的编译选项语法范围内,如Linux下的gcc 语法或Windows下的cl 语法。
(4)PTXAS_FLAGS
这个字符串扩展nvcc的命令选项–Xptxas,它主要是给CUDA内部工具ptxas传递一些命令。
(5)CUDAFE_FLAGS
这个字符串扩展nvcc的命令选项-Xcudafe,它主要是给CUDA内部工具cudafe传递一些命令。
(6)OPENCC_FLAGS
这个字符串扩展nvcc的命令选项–Xopencc,它主要是给CUDA内部工具nvopencc传递一些命令。

知识点2:

线程束:

在CUDA架构中,线程束是指一个包含32个线程的集合,这个线程集合被“编织在一起”并且“步调一致”的形式执行。在程序中的每一行,线程束中的每个线程都将在不同数据上执行相同的命令。

核函数(kernel):

  1. 在GPU上执行的函数通常称为核函数。
  2. 一般通过标识符global修饰,调用通过<<<参数1,参数2>>>,用于说明内核函数中的线程数量,以及线程是如何组织的。
  3. 以线程格(Grid)的形式组织,每个线程格由若干个线程块(block)组成,而每个线程块又由若干个线程(thread)组成。
  4. 是以block为单位执行的。
  5. 叧能在主机端代码中调用。
  6. 调用时必须声明内核函数的执行参数。
  7. 在编程时,必须先为kernel函数中用到的数组或变量分配好足够的空间,再调用kernel函数,否则在GPU计算时会发生错误,例如越界或报错,甚至导致蓝屏和死机。

<<<1,1>>>:核函数的调用,注意<<<1,1>>>,第一个1,代表线程格里只有一个线程块;第二个1,代表一个线程块里只有一个线程。

dim3结构类型

  1. dim3是基于uint3定义的矢量类型,相当于由3个unsigned int型组成的结构体。uint3类型有三个数据成员unsigned int x; unsigned int y; unsigned int z;
  2. 可使用于一维、二维或三维的索引来标识线程,构成一维、二维或三维线程块。
  3. dim3结构类型变量用在核函数调用的<<<,>>>中。
  4. 相关的几个内置变量
    4.1. threadIdx,顾名思义获取线程thread的ID索引;如果线程是一维的那么就取threadIdx.x,二维的还可以多取到一个值threadIdx.y,以此类推到三维threadIdx.z。
    4.2. blockIdx,线程块的ID索引;同样有blockIdx.x,blockIdx.y,blockIdx.z。
    4.3. blockDim,线程块的维度,同样有blockDim.x,blockDim.y,blockDim.z。
    4.4. gridDim,线程格的维度,同样有gridDim.x,gridDim.y,gridDim.z。
  5. 对于一维的block,线程的threadID=threadIdx.x。
  6. 对于大小为(blockDim.x, blockDim.y)的 二维 block,线程的threadID=threadIdx.x+threadIdx.y*blockDim.x。
  7. 对于大小为(blockDim.x, blockDim.y, blockDim.z)的 三维 block,线程的threadID=threadIdx.x+threadIdx.y*blockDim.x+threadIdx.z*blockDim.x*blockDim.y。
  8. 对于计算线程索引偏移增量为已启动线程的总数。如stride = blockDim.x * gridDim.x; threadId += stride。

了解自己GPU设备的性能

cudaError_t cudaStatus;  
    int num = 0;  
    cudaDeviceProp prop;  
    cudaStatus = cudaGetDeviceCount(&num);  //获得设备的数目
    for(int i = 0;i<num;i++)  
    {  
        cudaGetDeviceProperties(&prop,i);  //查看设备信息
    }  
cudaStatus = addWithCuda(c, a, b, arraySize); 

Prop指针中保存了所有设备相关的信息,可以单步调试查看。

知识点3:

凡是挂有“__global__”或者“__device__”前缀的函数,都是在GPU上运行的设备程序,不同的是__global__设备程序可被主机程序调用,而__device__设备程序则只能被设备程序调用。

利用stream来对操作进行并行处理,stream可以在一个设备上面运行多个核函数:

for(int i = 0;i<5;i++)  {  
     cudaStreamCreate(&stream[i]);   //创建流  
}  
// Launch a kernel on the GPU with one thread for each element.  
for(int i = 0;i<5;i++)  {  
    addKernel<<<1,1,0,stream[i]>>>(dev_c+i, dev_a+i, dev_b+i);    //执行流,可以传递参数。串行的执行
}  

我们可以看出,对于stream的使用,可以自己控制循环的参数,但是是串行的执行。
addKernel<<<1,1,0,stream[i]>>>(dev_c+i, dev_a+i, dev_b+i);中的第三个参数表示的是一个block块共享内存的大小,stream[i]表示的此时的核函数在哪个流上面执行。

扫描二维码关注公众号,回复: 2581280 查看本文章

猜你喜欢

转载自blog.csdn.net/zbzb1000/article/details/81432283