首页 > 其他分享 >CUDA指针数组Kernel函数

CUDA指针数组Kernel函数

时间:2024-03-07 17:44:18浏览次数:193  
标签:Kernel Host CUDA 数组 Device main 指针

技术背景

在前面的一篇文章中,我们介绍了在C++中使用指针数组的方式实现的一个不规则的二维数组。那么如果我们希望可以在CUDA中也能够使用到这种类似形式的不规则的数组,有没有办法可以直接实现呢?可能过程会稍微有一点麻烦,因为我们需要在Host和Device之间来回的转换,需要使用到很多CUDA内置的cudaMalloccudaMemcpy函数,以下做一个完整的介绍。

原始代码及修改思路

在上一篇文章中我们使用到的案例代码是这样的:

// 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上实现一个类似的功能,首先需要考虑到以下几个方面:

  1. 首先我们需要把数据拷贝到CUDA的Device Memory里面才能用来计算;
  2. Host侧和Device侧指针不能共享,也需要使用Memcpy来进行拷贝;
  3. 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

标签:Kernel,Host,CUDA,数组,Device,main,指针
From: https://www.cnblogs.com/dechinphy/p/18059316/cuda_ptr

相关文章

  • this指针的使用
    c++提供特殊的对象指针,也就是this指针,this指针指向被调用的成员函数所属的对象this指针是隐含每一个非静态成员函数内的一种指针this函数不需要定义,直接使用即可 this指针的用途:当形参和成员变量同名时,可用this指针来区分在类的非静态成员函数中返回对象本身,可使用return......
  • leetcode-15. 三数之和 - 双指针问题
    classSolution:defthreeSum(self,nums:List[int])->List[List[int]]:nums.sort()res=[]mem=set()foriinrange(len(nums)):ifnums[i]>0:breakifi>0andnum......
  • 【环境】24-03-05:CUDA与cuDNN的安装与下载
    CUDA提供通用并行计算平台和编程模型,CUDNN是针对深度学习应用进行优化后的GPU加速库。安装CUDA查看显卡型号和驱动版本(DriverVersion)打开cmd,输入nvidia-smi主要是确认CUDAVersion的版本,这里是12.4,意味着我可以安装12.4及以下任何版本的CUDA下载CUDACUDAToolkitArchive......
  • 【洛谷】明明的随机数(双指针去除重复元素)
    题目描述代码:#include<iostream>#include<algorithm>usingnamespacestd;intmain(){ intn; cin>>n; intA[n]; for(inti=0;i<n;i++){ cin>>A[i]; } sort(A,A+n); intslow=0,fast=0; while(fast<n){ if(slow!=......
  • SemanticKernel如何基于自有数据聊天
    效果使用gpt-3.5-turbo的效果什么是向量数据库?向量数据库是一种专为处理高维向量数据而设计的数据库系统。与传统的关系型数据库不同,向量数据库专注于存储和查询向量数据,例如图像、音频、文本等。其核心思想是将数据表示为向量形式,并通过向量之间的相似度来进行查询和分析。......
  • 20. 攻击牌的拖拽指针
    制作攻击指针首先创建一个ArrowLine的对象,然后给它添加LineRenderer,将线宽改为1.5,Materials改为RedArrowRedArrow就长这个样子鼠标点击攻击牌的时候出现指针二次贝塞尔曲线理论和实现方式相关代码publicvoidSetArrowPosition(){Vector3cardPo......
  • 科幻:Windows内核攻击是指针对Windows操作系统内核的恶意攻击行为
    Windows内核攻击是指针对Windows操作系统内核的恶意攻击行为。Windows内核作为操作系统的核心组件,控制着系统资源的管理、进程调度、设备访问等关键功能,因此成为黑客和恶意分子攻击的一个重要目标。以下是一些常见的Windows内核攻击方式:内核漏洞利用:黑客通过利用Windows内核中......
  • 编译后的opencv-cuda任意位置任意机器的移植(python版本
    测试环境:OS:Windowspython:3.10.11amd64opencv:4.9.0准备:复制build目录下面的install到目标路径,例如:d:\opencv-cuda490\install复制python目录下Lib\site-packages\cv2到目标路径,例如:d:\3.10.11-embed-opencv-cuda\Lib\site-packages修改:假如cv2的目标路径:......
  • 从Python语言的角度看C++的指针
    技术背景从一个PythonCoder的角度来说,其实很羡慕C++里面指针类型的用法,即时指针这种用法有可能会给程序带来众多的不稳定因素(据C++老Coder所说)。本文主要站在一个C++初学者的角度来学习一下指针的用法,当然,最好是带着一定的Python基础再去学习C++的逻辑,会更容易一些。内存地址赋......
  • C语言进行时——指针
    指针就是保存地址的变量指针运算符:&*&运算符获得变量的地址,它的操作数必须是变量,地址的大小是否与int相同取决于编译器。&不能对没有地址的东西去取地址。假定一个数组a[]&a=&a[0]指针的定义形式int*p=&i;(p指向i,p的值是i变量的地址)int*p,q(p是一个指针,q只是一个普通的变......