首先要了解什么是异构架构计算:即GPU和CPU协同工作,CPU端称为主机端用host表示,GPU端称为设备端用device表示。GPU和CPU连接一般协议是PCI-E,最新的协议有NVme,延迟更小。
程序执行流程主要分为六个大的部分:
①,定义线程的数据类型
GPU的SM里面包含很多Grid,每个Grid包含很多Block,每个Block包含很多Thread。
在分配线程时用到的数据类型定义是dim3,dim3就相当于一个三维的无符号整型。想象一下,SM里面存在一个三维的Grid,每个Grid里面存在一个三维的Block,每个Block里面存在一个三维的Tread,当然我们可以根据需要定义一维和二维的Grid和Block,不同的维度线程数量计算也会有不一样。
比如:
dim3 grid(3,2);//这里的3,2分别代表X方向上和Y方向上的block数量,即两行三列
表示我们所分配的每个grid里面都是如下所示的block索引:
(0,0)(1,0)(2,0)
(0,1)(1,1)(2,1)
dim3 block(4,3);
对应thread索引:
(0,0) (1,0) (2,0) (3,0)
(0,1) (1,1) (2,1) (3,1)
②,线程标识,threadIdx, blockIdx, blockDim, gridDim
每一个线程都可以用(blockIdx,threadIdx)来唯一标识,他们都是dim3类型的,blockIdx表示该线程是在第几个Grid里面,threadIdx表示该线程在某个block中的位置索引。
③,线程索引计算
在实际使用时,我们所分配的block数量和grid数量都比较多,并且分配的线程不止一维,因此需要找到一个对应关系。类似于在C语言中,我们访问二维向量时也是从第一行访问完然后第二行再依次访问。
比如我们现在分配一个一维的grid和一维的thread,来计算两个长度为6的一维向量相加
那么kernel函数中就应该
_global__ void addKernel(int *c, const int *a, const int *b)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
c[i] = a[i] + b[i];
}
有点类似于我们面前有一个二维矩阵,每一行都是1到10的数字,总共八行。我们需要找到某个数字在全局中为“第几个”,就得先计算:“这个数字前面有多少行”ד每一行满的时候多少个数字”+该数字为第几列。
再比如我们现在需要知道下图中红色位置线程在全局的索引,我们分配的是一维的grid,二维的block
我们首先需要计算该线程在“最后一个块中的索引”,即“该线程前面有多少行”ד每一行满的时候多少个线程”+“该线程为第几列”。,也就是threadIdx.y * blockDim.x + threadIdx.y
然后用这个值加上前面满格block中的线程数量即:blockDim.x * blockDim.y * blockIdx.x
完整代码如下:
__global__ void testBlockThread2(int *c, const int *a, const int *b)
{
int threadId_2D = threadIdx.x + threadIdx.y*blockDim.x;
int i = threadId_2D+ (blockDim.x*blockDim.y)*blockIdx.x;
c[i] = b[i] - a[i];
}
更多维度可以参考这个比较全面的链接:https://www.cnblogs.com/tiandsp/p/9458734.html
三,一个简单完整的向量相加代码,里面包含了大致执行流程
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include
__global__ void addKernel(int *C, const int *A, const int *B,int N)
{
//申请的一维grid二维block
int threadId_2D = threadIdx.x + threadIdx.y*blockDim.x;
int index = threadId_2D + (blockDim.x*blockDim.y)*blockIdx.x;
for (int i = index; i < N; i++)
{
C[i] = A[i] + B[i];
}
}
int main()
{
const int N = 30;
//申请host端内存
int h_A[N],h_B[N], h_C[N] = {0};
// 初始化host端数据
for (int i = 0; i < N; i++)
{
h_A[i] = 2;
h_B[i] = 3;
}
// 申请device内存
int *d_x, *d_y, *d_z;
cudaMalloc((void**)&d_x, N*sizeof(int));
cudaMalloc((void**)&d_y, N * sizeof(int));
cudaMalloc((void**)&d_z, N * sizeof(int));
// 将host数据拷贝到device
cudaMemcpy((void*)d_x, (void*)h_A, N * sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy((void*)d_y, (void*)h_B, N * sizeof(int), cudaMemcpyHostToDevice);
// 定义kernel的执行配置
dim3 blockSize(5,3);
dim3 gridSize(3);
// 执行kernel
addKernel << < gridSize, blockSize >> >(d_z, d_x, d_y, N);
// 将device得到的结果拷贝到host
cudaMemcpy((void*)h_C, (void*)d_z, N * sizeof(int), cudaMemcpyDeviceToHost);
//cudaDeviceSynchronize();
//输出结果
for(int i = 0; i < N;i++ )
{
printf("%d\n", h_C[i]);
}
// 释放device内存
cudaFree(d_x);
cudaFree(d_y);
cudaFree(d_z);
// 释放host内存
free(h_A);
free(h_B);
free(h_C);
return 0;
}