CUDA指针数组Kernel函数
技术背景
在前面的一篇文章中,我们介绍了在C++中使用指针数组的方式实现的一个不规则的二维数组。那么如果我们希望可以在CUDA中也能够使用到这种类似形式的不规则的数组,有没有办法可以直接实现呢?可能过程会稍微有一点麻烦,因为我们需要在Host和Device之间来回的转换,需要使用到很多CUDA内置的cudaMalloc和cudaMemcpy函数,以下做一个完整的介绍。
原始代码及修改思路
在上一篇文章中我们使用到的案例代码是这样的:
// g++ main.cpp -o main && ./main
#include <iostream>
struct bucket{
int num;
int *ptr;
};
void print_bucket(bucket *bc, int shape[]){
for (int i=0; i<4; i++){
bucket bc_i = bc[i];
printf("%d: ", bc_i.num);
for (int j=0; j<shape[i]; j++){
printf("%d,", bc_i.ptr[j]);
}
printf("\n");
}
}
int main(){
// 定长数组
int arr[4][3] = {{0,1,2},{1,2,3},{2,3,4},{3,4,5}};
// 有效长度
int shape[4] = {2,3,2,1};
// 先构建结构体数组
bucket _bc[4];
for (int i=0; i<4; i++){
_bc[i].num = shape[i];
_bc[i].ptr = arr[i];
_bc[i].ptr += 3-shape[i];
}
// 再把结构体数组赋值给结构体指针
bucket *bc = _bc;
// 打印结构体的所有内容
print_bucket(bc, shape);
return 0;
}
通过定义一个bucket结构体,用双重的指针数组实现了一个不规则数组的存储。第一重的指针对应于不规则数组的第一个维度,这里长度一般是固定的。第二重的指针指向不规则数组的第二个维度,这个维度的长度大小是不一致的,因为我们在结构体中存储的只是一个指针和该维度的数组长度,因此可以实现不规则数组的存储。那么上述代码的运行结果为:
$ g++ main.cpp -o main && ./main
2: 1,2,
3: 1,2,3,
2: 3,4,
1: 5,
打印的第一列是当前数组的长度,也就是不规则数组的第二个维度。后面的数字是对应的数组内容,当然,这里需要注意的点是,我们在初始化的时候,尤其是跟Python等语言进行交互的时候,初始化阶段使用的还是一个固定长度的Tensor,而不需要使用的那些位置需要填充或者叫padding一些数字,常见的就是-1和0。
那么如果我们希望可以在CUDA上实现一个类似的功能,首先需要考虑到以下几个方面:
- 首先我们需要把数据拷贝到CUDA的Device Memory里面才能用来计算;
- Host侧和Device侧指针不能共享,也需要使用Memcpy来进行拷贝;
- Kernel函数需要分配一定的计算资源,关于GPU计算资源分配的内容,可以参考之前写的这一篇博客。
CUDA实现
根据以上提到的几个修改点,我们可以这样逐个解决:分别在Host侧定义好相关的数组、指针和结构体之后,使用CUDA的内置函数将相应的内容拷贝到Device侧,两侧同时保留数据,所有的数据更新也都在CUDA上实现。如果有回传数据的需要,我们再把最终的Device侧数据拷贝到Host侧进行同步。完成CUDA的计算之后,同步所有CUDA的线程,并且释放不必要的内存。以下是具体代码实现:
// 文件名:main.cu
// 编译运行指令:nvcc -Xcompiler -fPIC -o main main.cu && ./main
#include <iostream>
#include "cuda_runtime.h"
struct bucket{
int num;
int *ptr;
};
// CUDA Kernel函数,该函数主要用于打印bucket结构体的内部数据
__global__ void print_bucket_cuda(bucket *bc, int *shape){
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < 4){
bucket bc_i = bc[i];
for (int j=0; j<shape[i]; j++){
printf("%d %d\n", i, bc_i.ptr[j]);
}
}
}
int main(){
// 定义Host侧数据
int arr[4][3] = {{0,1,2},{1,2,3},{2,3,4},{3,4,5}};
int shape[4] = {2,3,2,1};
// 先定义Host侧结构体,但是第二重指针在Device侧分配和定义
bucket _bc[4];
for (int i=0; i<4; i++){
_bc[i].num = shape[i];
cudaMalloc((void**)&(_bc[i].ptr), shape[i]*4);
cudaMemcpy(_bc[i].ptr, arr[i]+3-shape[i], shape[i]*4, cudaMemcpyHostToDevice);
}
// 定义Device侧的结构体
bucket *d_bc;
cudaMalloc((void**)&d_bc, sizeof(bucket)*4);
int *d_shape;
cudaMalloc((void**)&d_shape, sizeof(int)*4);
// 将Host侧结构体拷贝到Device侧
cudaMemcpy(d_bc, _bc, sizeof(bucket)*4, cudaMemcpyHostToDevice);
cudaMemcpy(d_shape, shape, sizeof(int)*4, cudaMemcpyHostToDevice);
// 运行Kernel打印函数
print_bucket_cuda<<<4, 1>>>(d_bc, d_shape);
// CUDA线程同步
cudaDeviceSynchronize();
// 释放CUDA显存
cudaFree(d_bc);
cudaFree(d_shape);
return 0;
}
在这个实现中,比较重要的一个难点是,我们从Host侧拷贝一个双重指针去Device侧,如果直接拷贝第一重的指针,会出现一个问题是在Device侧无法读取在Host上存储的第二重指针的数据。因此我们在Host侧拷贝数据给Device侧时,我们应该先定义一个Host侧的结构体,但该结构体的第二重指针应该指向Device侧的内存。然后再将第一重的指针拷贝到Device侧,这样才完成了整个结构体的内容拷贝,在Device上才可以识别。该代码的运行结果如下所示:
$ nvcc -Xcompiler -fPIC -o main main.cu && ./main
2 3
3 5
1 1
0 1
2 4
0 2
1 2
1 3
这里是乱序的打印,因为CUDA在计算时几乎是同一时间完成的,因此打印任务也是同时执行的,至于哪一个结果先被输出出来,其实是有一定的随机性的。但是通过对比,我们发现这里输出的数据内容跟前面C++的代码输出内容是一致的。第一列的数据表示第一个维度的索引ID,如果输出是0也就对应上面C++输出的第一行内容。例如这里首位是0的数据,第二列对应元素有1和2,这里就跟C++第一行输出的数组内容对应上了。
总结概要
继上一篇文章学习使用C++存储一个不规则二维数组之后,这里介绍如何在C语言版的CUDA中实现一个不规则的二维数组。总体的实现思路跟前面一篇文章一样,使用了一个二维的指针数组来存储。其中主要的不同点大概就是在Host和Device之间的内存交互上,需要不断的分配、拷贝和释放内存,最终我们还是用一个CUDA的Kernel函数实现了一个不规则数组的输出。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/cuda_ptr.html
作者ID:DechinPhy
更多原著文章:https://www.cnblogs.com/dechinphy/
请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html
CUDA指针数组Kernel函数的更多相关文章
- C++中的指针、数组指针与指针数组、函数指针与指针函数
C++中的指针.数组指针与指针数组.函数指针与指针函数 本文从刚開始学习的人的角度,深入浅出地具体解释什么是指针.怎样使用指针.怎样定义指针.怎样定义数组指针和函数指针.并给出相应的实例演示.接着,差 ...
- golang中数组指针和指针数组当做函数参数如何修改数组中的值
先理解:数组指针它的类型时指针,指针数组它的类型时数组 1. 数组指针当做函数的参数 package main import "fmt" func changeData(dataA ...
- C++数组指针、指针数组、函数指针的核心概念
1.什么叫数组指针? 数组指针:一个指向一维或者多维数组的指针. 比如:int * b=new int[10];指向一维数组的指针b ; 注意,这个时候释放空间一定要delete [] ,否则会造成内 ...
- C语言--- 高级指针2(结构体指针,数组作为函数参数)
一.结构体指针 1. 什么是结构体指针?指向结构体变量的指针 结构体: typedef struct stu{ char name[ ...
- C/C++ 一段代码区分数组指针|指针数组|函数指针|函数指针数组
#include<stdio.h> #include<stdlib.h> #include<windows.h> /* 举列子说明什么是函数指针 */ //以一个加 ...
- C++二级指针第一种内存模型(指针数组)
二级指针第一种内存模型(指针数组) 指针的输入特性:在主调函数里面分配内存,在被调用函数里面使用指针的输出特性:在被调用函数里面分配内存,主要是把运算结果甩出来 指针数组 在C语言和C++语言中,数组 ...
- 嵌入式-C语言基础:指针数组(和数组指针区分开来)
指针数组:一个数组,若其元素均为指针类型的数据,称为指针数组,指针数组存放的是指针类型的数据,也就是指针数组的每个元素都存放一个地址.下面定义一个指针数组: int * p[4];//[]的优先级是比 ...
- CUDA学习,第一个kernel函数及代码讲解
前一篇CUDA学习,我们已经完成了编程环境的配置,现在我们继续深入去了解CUDA编程.本博文分为三个部分,第一部分给出一个代码示例,第二部分对代码进行讲解,第三部分根据这个例子介绍如何部署和发起一个k ...
- C与指针(结构体指针,函数指针,数组指针,指针数组)定义与使用
类型 普通指针 指针数组(非指针类型) 数组指针 结构体指针 函数指针 二重指针 定义方式 int *p; int *p[5]; int (*p)[5]; int a[3][5]; struct{.. ...
- C++基础——函数指针 函数指针数组
==================================声明================================== 本文版权归作者所有. 本文原创,转载必须在正文中显要地注明 ...
随机推荐
- SP5464 CT - Counting triangles 题解
题目翻译 题意 有一个网格,左上角是 \((0,0)\),右上角是 \((x,y)\).求这个网格中一共有多少个等腰直角三角形. 输入 第一行给定一个 \(c\),表示有 \(c\) 组数据. 后面 ...
- python实现zip分卷压缩与解压
1. python实现zip分卷压缩 WinHex 开始16进制一个一个文件对比 WinRar 创建的分卷压缩和单个 zip 文件的差异. 如果想把单个大文件 test.zip -> 分卷文件 ...
- 机器学习算法(四): 基于支持向量机的分类预测(SVM)
机器学习算法(四): 基于支持向量机的分类预测 本项目链接:https://www.heywhale.com/home/column/64141d6b1c8c8b518ba97dcc 1.相关流程 支 ...
- ChatGPT 对接微信公众号技术方案实现!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 9天假期写了8天代码和10篇文章,这个5.1过的很爽! 如假期前小傅哥的计划一样,这个假期开启 ...
- 创建framework静态库和.a静态库
在APP项目中使用的静态库有两种,一是.a静态库,另一种是framework静态库.下面分布介绍这2中静态库的创建过程,以及通过脚本工具做自动化打包的2种方式. Framework静态库生成 如果 ...
- 每日一道Java面试题:说一说Java中的异常
写在开头 任何一个程序都无法保证100%的正常运行,程序发生故障的场景,我们称之为:异常,在Java中对于异常的处理有一套完善的体系,今天我们就来一起学习一下. 老样子,用一段简单的代码开始今天的学习 ...
- 使用Dapr和.NET 6.0进行微服务实战系列
大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. 本文是<使用Dapr和.NET 6.0进行微服务实战>的第1篇引言部分 ...
- STM32 HAL库 USART DMA驱动
前言 本文是在使用 STM32L4 的串口 DMA 功能时,使用 HAL 库出现的一些问题,通过以下方式解决了 HAL 库中存在 DMA 发送和接收的一些问题. STM32L4 的 DMA 简介 DM ...
- NebulaGraph入门介绍
NebulaGraph入门介绍 什么是图数据库? 图数据库就会是存储图形网络并能从中检索信息的数据库. 图数据库在处理关联关系上有极大的优势,它以图论为理论基础,使用图模型,将关联数据的实体作为顶点( ...
- NC17877 整数序列
题目链接 题目 题目描述 给出一个长度为n的整数序列 \(a_1,a_2,...,a_n\) ,进行 \(m\) 次操作,操作分为两类. 操作1:给出 \(l,r,v\) ,将 \(a_l,a_{l+ ...