1. 什么是CUDA?
CUDA全称是Compute Unified Device Architecture,中文名称即统一计算设备架构,它是NVIDIA公司提出了一种通用的并行计算平台和编程模型。使用CUDA,我们可以开发出同时在CPU和GPU上运行的通用计算程序,更加高效地利用现有硬件进行计算。
并行编程的中心思想是分而治之:一个问题可以首先被粗粒度地划分为若干较小的子问题,CUDA使用被称为块(Block)的单元来处理它们,每个块都由一些CUDA线程组成,线程是CUDA中最小的处理单元,将这些较小的子问题进一步划分为若干更小的细粒度的问题,我们便可以使用线程来解决这些问题了。对于一个普通的NVIDIA GPU,其CUDA线程数目通常能达到数千个甚至更多,因此,这样的问题划分模型便可以成倍地提升计算机的运算性能。
2. CUDA基本概念
本节将介绍CUDA的一些基本的编程概念,该节用到的例子来自于CUDA Sample中的VectorAdd项目。
2.1 内核(Kernels)
CUDA C是C语言的一个扩展,它允许程序员定义一种被称为内核函数(Kernel Functions)的C函数,内核函数运行在GPU上,一旦启动,CUDA中的每一个线程都将会同时并行地执行内核函数中的代码。
内核函数使用关键字_global_来声明,运行该函数的CUDA线程数则通过<<<…>>>执行配置语法来设置。(参见章节”C语言扩展”),每一个执行内核函数的线程都由一个唯一的线程ID,这一ID可以通过在内核函数中访问threadIdx变量来得到。
下面通过一些示例代码来展示刚刚提到的这些概念该如何应用在编程中:
// 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);
...
}
在上面的代码中,N个线程将会并行地同时执行加法运算。
每个线程块(block)中的线程数量是有限制的,因为依据前面所说,同一线程块(block)中的所有线程都会被分配到同一个处理器核上运行,共享有限的存储资源,因此对于当前的GPU,线程块所能包含的最大线程数目为1024。
上面的例子中numBlocks代表线程块的数量,这里的值为1。在一般的CUDA程序中,这个值通常大于1,也就是说将会有多个线程块被分配到多个处理器核中同时进行处理,这样就大大提高了程序的并行性。
在CUDA中,线程块包含在线程格(grid)当中,线程格可以是一维、二维或者三维的,线程格的尺寸一般根据待处理数据的规模或者处理器的数量来指定。线程格中所包含的线程块数目通常远远大于GPU处理器核心的数目。下图展示了线程格(grid)、线程块(block)以及线程(thread)之间的关系:
因此:
内核函数的调用可以简化为kernel<<<A,B>>>(parameters)
,
1. gridDim:代表线程格(grid)的尺寸,gridDim.x为x轴尺寸,gridDim.y、gridDim.z类似。拿上图来说,它的gridDim.x = 3,gridDim.y = 2,gridDim.z = 1。 2. blockIdx:代表线程块(block)在线程格(grid)中的索引值,拿上图来说,Block(1,1)的索引值为:blockIdx.x = 1,blockIdx.y = 1。
2. blockDim:代表线程块(block)的尺寸,blockDIm.x为x轴尺寸,其它依此类推。拿上图来说,注意到Block(1,1)包含了4 * 3个线程,因此blockDim.x = 4, blockDim.y = 3。
3. threadIdx:线程索引,前面章节已经详细探讨过了,这里不再赘述。明白了这些变量的含义,那么下面的矩阵加法程序便不难理解了:
// 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)
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);
MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
...
}
在上面的程序中,线程块(block)的尺寸是16x16,这是CUDA编程中一个非常普遍的选择。线程格(grid)包含了足够多的线程块(block)来进行计算。
线程块(block)是独立执行的,在执行的过程中线程块之间互不干扰,因此它们的执行顺序是随机的。
同一线程块中的线程可以通过访问共享内存(shared memory)或者通过同步函数__syncthreads()来协调合作。这些概念将在以后的章节中详细解释。