CUDA 编程--线程层次

原文地址:https://blog.csdn.net/u012033124/article/details/70792877

1.内核

CUDA通过允许程序员定义称为内核的C函数扩展了C,内核调用时会被N个CUDA线程执行N次(译者注:这句话要好好理解,其实每个线程只执 
行了一次),这和普通的C函数只执行一次不同。 
内核使用 global 声明符定义,使用一种新<<< … >>>执行配置语法指定执行某一指定内核的线程数(参看下面代码)。每个执行内核的线程拥有一个独一无二的线程ID,可以通过内置的threadIdx变量在内核中访问(译者注:这只说明在块内是唯一的,并不一定是全局唯一的)。 
下面的样本代码将两个长度为N的向量A和B相加,并将结果存入向 
量C中。

//代码1:一维线程网格,一维线程块示例
// Kernel definition
__global__ void VecAdd(float* A, float* B, float* C)
{
    int i = threadIdx.x;
    C[i] = A[i] + B[i];
}
int main()
{
    ...
    // Kernel invocation with N threads
    VecAdd<<<1, N>>>(A, B, C);
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里,VecAdd<<<1, N>>>(A, B, C);语法调用了1个线程块,其中包含N个线程. N个线程中的每一个执行VecAdd()的一次成对加法(译者注:由于只使用了一个块,因此线程ID是唯一的)。

第4行threadIdx是一个CUDA内置变量,类型为uint3,用于块内的线程索引(也可通过字面意思理解,thread Index).这里提到的uint3是cuda的一个内置变量类型,继承自基本整形和浮点型,为结构体,包含3个成员x,y和z.还有其他的变量类型如uint1,uint2,uint4,顾名思义分别包含1,2,4个成员,uint4包含4个成员x,y,z,w,以此类推.这里u表示无符号数,与之对应有int1,int2,int3,int4变量类型,是有符号数.

这些结构体均附带形式为make<typename >的构造函数,示例如下:

int2 make int2(int x, int y);
  • 1

这将创建一个类型为int2 的向量,值为(x, y).

2.线程层次

为简便起见, threadIdx是一个有3个分量的向量,所以线程可以使用一维,二维,三维索引标识,形成一维,二维,三维的线程块。这提供了一种自然的方式来调用作用在域内元素上的计算,如向量,矩阵,体元(volume)。 
线程索引和线程ID直接相关:对于一维的块,它们相同;对于二维长度为( Dx,Dy)的块,线程索引为( x,y)的线程ID(x+yDx);对于三维长度为(Dx,Dy,Dz)的块,索引为( x,y,z)的线程ID为( x+yDx+zDxDy)(译者注:这和我们使用C数组的方式不一样,大家注意理解)。 
下面的例子代码将两个长度为N*N的矩阵A和B相加,然后将结果写入矩阵C。

//代码2:一维线程网格,2维线程块示例
// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N],
float C[N][N])
{
    int i = threadIdx.x;
    int j = threadIdx.y;
    C[i][j] = A[i][j] + B[i][j];
}
int main()
{
    ...
    // Kernel invocation with one block of N * N * 1 threads
    int numBlocks = 1;
    dim3 threadsPerBlock(N, N);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

线程块内线程数和网格内线程块数由<<< A,B >>>语法确定,A表示块数,B表示块内线程数.A,B可以是整形或者dim3类型。(这里的dim3是一种整形向量类型,基于用于指定维度的uint3。在定义类型为dim3 的变量时,未指定的任何组件都将初始化为1)因此15行dim3 threadsPerBlock(N, N);表示创建了一个二维N*N的dim3变量(第三维为1),用于块内线程索引.

之后16行调用MatAdd函数,使用<<< numBlocks, threadsPerBlock >>> 语法将threadsPerBlock交给内置变量threadIdx(注意threadIdx并不是一个常量,而是”索引”,它的范围是0~N-1,否则内核函数MatAdd就只计算了矩阵的一个元素而已,肯定不可能是这样,而是遍历了0~N-1的每个情况)

第5~7行的内核代码使用了线程索引threadIdx的x和y分量来确定二维线程块中的一个线程(x,y),每个线程并行地执行矩阵某个元素的加法C[i][j] = A[i][j] + B[i][j];由于每个加法之间无关可以完全并行化,因此大大提高了执行速度.

由于块内的所有线程必须存在于同一个处理器核心中且共享该核心有限的存储器资源,因此,一个块内的线程数目是有限的。在目前的GPU上,一个线程块可以包含多达1024个线程。 
然而,一个内核可被多个同样大小的线程块执行,所以总的线程数等于每个块内的线程数乘以线程块数。 
线程块被组织成一维、二维或三维的线程网格,如下图所示。一个网格内的线程块数往往由被处理的数据量而不是系统的处理器数决定,前者往往远超后者。 

网格内的每个块可以通过一维、二维或三维索引唯一确定,在内核中此索 
引可通过内置的blockIdx变量访问。块的尺寸(dimension)可以在内核中通过内 
置变量blockDim访问。

为了处理多个块,扩展前面的MatAdd()例子后,代码成了下面的样子。

//代码3:二维线程网格,2维线程块示例
// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N],
float C[N][N])
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;//先确定哪一块,再确定块内偏移
    int j = blockIdx.y * blockDim.y + threadIdx.y;//同理
    if (i < N && j < N)//分块时可能有非整除情况,防止超出索引范围N
    C[i][j] = A[i][j] + B[i][j];
}
int main()
{
    ...
    // Kernel invocation
    dim3 threadsPerBlock(16, 16);
    dim3 numBlocks(N / threadsPerBlock.x, N / threadsPerBlock.y);//因为是从0开始计数,这样已经考虑了非整除的情况,自己计算体会一下
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里用到了两个新的内置变量类型blockIdx和blockDim.其中blockIdx是uint3类型,表示块索引(block Index).由<<< A,B >>>语法中的A指定,代码中A参数是2维dim3变量numBlocks,因此blockIdx也是二维变量,x分量和y分量分别为N / threadsPerBlock.x和N / threadsPerBlock.y 
blockDim是dim3类型,表示块的维度,由<<< A,B >>>语法中的B指定,此处由dim3 threadsPerBlock(16, 16);指定,不难理解.

第6行和第8行的索引方式,分别对x和y,先找块,再找块内偏移.用下图比较直观 

总结: 
几个内置变量: 
1.threadIdx 
类型为uint3,表示块内线程索引,可以是一维,二维或三维,由<<< A,B >>>语法中的B参数指定

2.blockIdx 
类型为uint3,表示网格内块索引,可以是一维,二维或三维,由<<< A,B >>>语法中的A参数指定

3.blockDim 
类型为dim3,表示块的维度,可以是一维,二维或三维,由<<< A,B >>>语法中的B参数指定

注意:虽然threadIdx与blockDim同为B参数指定,但是有重要区别:threadIdx是一个可变量,用于索引.blockDim是一个常量,一旦确定后值就不变.blockIdx也是一个用于索引的可变量)

4.gridDim 
类型为dim3,表示网格的维度

5.warpSize 
int,表示以线程为单位的warp块大小.

参考: 
1.风辰< cuda编程指南5.0 > 
2.http://blog.csdn.net/qq_34488063/article/details/52162454


猜你喜欢

转载自blog.csdn.net/metal1/article/details/81060454