CUDA学习笔记二---编程模型

一,关于编程模型

首先要了解什么是异构架构计算:即GPU和CPU协同工作,CPU端称为主机端用host表示,GPU端称为设备端用device表示。GPU和CPU连接一般协议是PCI-E,最新的协议有NVme,延迟更小。

程序执行流程主要分为六个大的部分:

  1. 在host端分配内存,进行数据初始化。
  2. 在device端分配内存。
  3. 将数据从host拷贝到device。
  4. 用CUDA核函数在device端完成指定的运算。
  5. 将运算结果从device端拷贝回host端。
  6. 将host和device上之前分配的内存释放掉。

二,关于线程的分配

①,定义线程的数据类型

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中的位置索引。

  • blockIdx包含三个值:blockIdx.x 和 blockIdx.y 和 blockIdx.z
  • threadIdx也包含三个值:threadIdx.x 和 threadIdx.y 和 threadIdx.z
  • blockDim表示一个block的大小,即一个block包含多少个Thread,也可以写成blockDim.x,blockDim.y,blockDim.z
  • gridDim表示一个grid的大小,即一个grid里面包含多少个block,同理也可以写成三维结构
  • 如:blockIdx.x表示该线程在block中X方面的坐标,即第几列。blockIdx.y表示该线程在Y方向的坐标,即第几行。blockDim.x表示一个block里面在X方面一共有几个值,即列数,blockDim.y表示一个block里面在Y轴上一共有几个值,即行数。

③,线程索引计算
在实际使用时,我们所分配的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];
}

CUDA学习笔记二---编程模型_第1张图片
有点类似于我们面前有一个二维矩阵,每一行都是1到10的数字,总共八行。我们需要找到某个数字在全局中为“第几个”,就得先计算:“这个数字前面有多少行”ד每一行满的时候多少个数字”+该数字为第几列。

再比如我们现在需要知道下图中红色位置线程在全局的索引,我们分配的是一维的grid,二维的block

CUDA学习笔记二---编程模型_第2张图片
我们首先需要计算该线程在“最后一个块中的索引”,即“该线程前面有多少行”ד每一行满的时候多少个线程”+“该线程为第几列”。,也就是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;

	
    
 }

    




结果:
CUDA学习笔记二---编程模型_第3张图片

你可能感兴趣的:(CUDA并行计算学习笔记,cuda,gpu,并行计算)