并行编程实战——CUDA编程的变量类型限定和内置变量

一、CUDA的变量类型限定

CUDA中的数据类型以向量类型的操作为主(普通变量可以看成一维的向量),向量的理解大家可以有很多种方法,一种最贴近的就是数学中的向量即一个多维度的数的组。简单的说明就是一个人可以有身高、体重、年龄等多个数字来描述;一个飞机的坐标可以用距离、高度和方向来描述等待。

在CUDA中向量其实也是类似如此,不过其可能更抽象一些。对于这些向量类型,CUDA中进行了特殊的限定,主要包含:

  1. device _
    被此限定符限定的变量,只能驻留在设备上。它与下面的2、3和4其中之一(注意,只能之一)一起来更具体的限定变量的内存空间。如果它们都没有出现则:
    --变量驻留在全局内存空间
    --具有创建它的CUDA上下文的生命周期
    --每个设备均有一个不同的对象
    --可以从网格内所有线程或通过运行时库从主机对其进行访问
    2.
    shared _
    它可以配合__device__一上进使用,如果使用这个限定符限定了变量,则说明:
    --变量驻留在线程块的共享内存空间
    --具有块的生命周期
    --每个块都有一个不同的对象
    --只能从块的线程访问
    --没有固定地址
c 复制代码
extern __shared__ float array[];
__device__ void func()      // __device__ or __global__ function
{
    short* array0 = (short*)array;
    float* array1 = (float*)&array0[128];
    int*   array2 =   (int*)&array1[64];
}
  1. _constant _
    它可以配合__device__一上进使用,如果使用这个限定符限定了变量,则说明:
    --变量驻留在常量内存空间
    --具有创建它的CUDA上下文的生命周期
    --每个设备都有一个不同的对象
    --可以从网格内所有线程或通过运行时库从主机对其进行访问
    --当其它并发网格在此网格的生命周期访问该常量时,从主机修改此常量的行为为UB行为
  2. _grid_constant _
    在大于7.0之后,它声明了一个非引用类型的__global__函数的形参,即:
    --拥有网格的一样的生命周期
    --网格私有的,即主机线程和其它网格(含子网格)都无法访问此对象
    --每个网格具有一个不同的对象,即每个网格中的线程看到的都是同一地址
    --其是只读的,即修改其会引起UB行为(包括mutable成员变量)
c 复制代码
__device__ void unknown_function(S const&);
__global__ void kernel(const __grid_constant__ S s) {
   s.x += threadIdx.x;  // Undefined Behavior: tried to modify read-only memory

   // Compiler will _not_ create a per-thread thread local copy of "s":
   unknown_function(s);
}
  1. _managed _
    它可以与__device__选择隆的一起使用,如果使用其则:
    --可从设备和主机代码中引用,比如它的地址可以获取或可以直接在设备或主机函数中读写
    --其具有应用程序的生命周期
  2. _restrict _
    nvcc通过此关键字来支持受限制的指针,这和C语言中的受限指针的应用类似,主要是提高了编译器优化的能力。但在CUDA的某些场景中反而可能降低其性能。
c 复制代码
void foo(const float* __restrict__ a,
         const float* __restrict__ b,
         float* __restrict__ c)
{
    float t0 = a[0];
    float t1 = b[0];
    float t2 = t0 * t1;
    float t3 = a[1];
    c[0] = t2;
    c[1] = t2;
    c[4] = t2;
    c[2] = t2 * t3;
    c[3] = t0 * t3;
    c[5] = t1;
    ...
}

二、CUDA的内置变量

正如在一些编译器或库中,都保留了一些关键字或提前定义了一些宏、全局变量等,在CUDA中也定义了一些内置变量:

  1. gridDim:网格的维度的变量,dim3类型
  2. blockIdx:块的索引变量,uint3类型
  3. blockDim:块的维度变量,dim3类型
  4. threadIdx:块内的线程索引变量,unit3类型
  5. warpSize:线程中的wrap的大小,int类型
    这里需要说明一下dim3,它就是基于unit3向量类型。默认初始化均为1,主要用来定义尺寸大小。
    在前面的代码中,已经复用过这些变量进行了一些细节的数据处理,大家可以回头看看。此处不再重复给例子。

三、总结

这里并没有从最基础的数据类型讲起,因为CUDA学习就假定了开发者对C++有一定的开发经验。如果确实有些细节没有看明白,可以看一下官方的开发文档,再看一些C++相关的书籍即可明白。

CUDA在基础语义上可以理解为是对C++进行了一些额外的扩展,所以只要抓住不同点就可以了,在实践中不断的学习就可以对此理解得愈发的清晰,所以并不要急于求成,非要一步达到什么水平。