首页 > 编程知识 正文

CUDA编程 2向量加法,平面向量的加法

时间:2023-05-05 07:38:53 阅读:275511 作者:3737

本文介绍CUDA环境下两个向量的加法运算。代码运行的系统环境为操作系统Ubuntu 16.04 LTS 64位,CUDA版本7.5,GCC版本5.4.0。项目Github下载地址为:CUDA向量加法Github项目


1. CUDA代码分析和实现 Step 1: 关于Host/Device 数组指针

CUDA编程而言,我们习惯把CPU端称为Host,GPU端称为Device。基于Host端和Device端拥有各自不同的两份内存地址空间的事实,代码层面上要求编程人员一方面维护两份指针地址,即一份指向Host端内存的指针地址,一份指向Device端显存空间的指针地址;另一方面必要时需要对Host端和Device端的内存数据进行相互访存,拷贝数据。

Host端内存指针,

int *a_host;int *b_host;int *c_host;int *devresult_host;

Device端内存指针,

a_host = (int *)malloc(arraysize*sizeof(int));b_host = (int *)malloc(arraysize*sizeof(int));c_host = (int *)malloc(arraysize*sizeof(int));

Host/Device端内存数据拷贝操作,

cudaMemcpy(a_dev, a_host, arraysize*sizeof(int), cudaMemcpyHostToDevice);cudaMemcpy(b_dev, b_host, arraysize*sizeof(int), cudaMemcpyHostToDevice); Step 2: 关于Device 并行线程配置

dim3 dimBlock()和dim3 dimGrid()是Device端关于线程配置的两种数据结构,该数据结构具有两个层面的含义:其一,初始化并行线程的数量;其二,初始化这些并行线程的空间组织方式,即并行线程的维度。blocks/threads的组织方式通常出于编程方便上的考虑。

例1:给定总共1024个threads和4个blocks,问:如果blocks以一维空间形式组织,threads以一维空间形式组织,列举一个可行的并行线程调度模型?

int blocksize = 256;int blocknum = 4;dim3 dimBlock(blocksize, 1, 1);dim3 dimGrid(blocknum, 1, 1);

例2:给定总共2048个threads和4个blocks,问:如果blocks以二维空间形式组织,threads以一维空间形式组织,列举一个可行的并行线程调度模型?

int blocksize = 256;int blocknum = 2;dim3 dimBlock(blocksize, 1, 1);dim3 dimGrid(blocknum, blocknum, 1);

例3:给定总共2048个threads和4个blocks,问:如果blocks以二维空间形式组织,threads以二维空间形式组织,列举一个可行的并行线程调度模型?

int blocksize = 16;int blocknum = 2;dim3 dimBlock(blocksize, blocksize, 1);dim3 dimGrid(blocknum, blocknum, 1);

本文中,blocks数量为blocknum,threads的数量 (每个block中包含的线程数量) 为512。blocks和threads均采用一维空间形式组织,即blocks的编号为0, 1, 2, 3, …, blocknum -1,每个block内部threads的编号为0, 1, 2, 3, …, 511。因此,我们可以用tid来表示这blocknum × times × 512个并行线程的唯一编号,如下,

int tid = blockIdx.x * blockDim.x + threadIdx.x

注:一维空间形式组织的blockDim.y = 1, blockDim.z=1, threadIdx.y=1 且 threadIdx.z = 1.

Step 3: 关于kernel的实现和调用

在数据从Host端拷贝到Device端之后,我们需要在Device端构造Kernel函数来计算这些数据。Kernel是CUDA并行结构的核心部分,一个kernel函数可以一次诱发多个线程并发运行,比方说,给定一个warp大小32的情况下,一个kernel函数可以诱发tid=0, 1, 2, …, 21的线程同时并行计算数据。如下,

__global__ void add_in_parallel(int *array_a, int *array_b, int *array_c){ int tid = blockIdx.x * blockDim.x + threadIdx.x; array_c[tid] = array_a[tid] + array_b[tid];}

__global__ 标识符表示add_in_parallel()函数是一个Device端运行的kernel函数。

CUDA编译器nvcc是一个修改版的C编译器,语法上两者的函数调用方式高度相似,如下,

add_in_parallel<<<dimGrid, dimBlock>>>(a_dev, b_dev, c_dev);

注:Kernel函数的调用需要事先定义blocks/threads的数量和空间组织形式,

Step 4: 关于Device 计算结果

将Device端的计算结果返回到Host端,如下,

cudaMemcpy(devresult_host, c_dev, arraysize*sizeof(int), cudaMemcpyDeviceToHost);

CPU端的计算结果,如下,

for (int i = 0; i < arraysize; i++){ a_host[i] = i; b_host[i] = i; c_host[i] = a_host[i] + b_host[i];}

校验CPU/GPU两者的计算结果的一致性,如下,

for (int i = 0; i < arraysize; i++){ if (c_host[i]!=devresult_host[i]) { status = 1; }}if (status){ printf("Failed vervified.n");}else{ printf("Sucessdully verified.n");} Step 5: 关于释放内存

释放Host端的内存,如下,

free(a_host);free(b_host);free(c_host);

释放Device端的内存,如下,

cudaFree(a_dev);cudaFree(b_dev);cudaFree(c_dev);
2. 编译调试

源代码:vec_add.cu

编译,如下,

$ nvcc vec_add.cu -o vec_add

运行,如下,

$ ./vec_add
3. 更多改进版本 Version 1: 增加计时器,包括数据拷贝耗时,kernel耗时

源代码:vec_add_count.cu

Version 2: 增加GPU kernel warm up()函数

源代码:vec_add_warmup.cu

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。