CUDA GPU编程中使用结构体传递函数参数

CUDA GPU编程中使用结构体传递函数参数

 

         CUDA GPU编程中,虽然统一寻址(Unified Memory)技术能够大大简化编程难度和代码复杂度,但是速度略有牺牲,同时对运行环境提出更多的要求。而在不使用这项技术时,编程时需要同时创建CPU(host)和GPU(device)端的变量指针,然后为其分别分配内存。操作完成后,再分别释放内存。CUDA工程的范例程序中,单独编写了__global__函数,并将其和设备内存的创建,使用和释放包装在了一个函数里面,最终将这个包装后的函数提供给用户。用户只需管理自己的CPU内存,以及简单地调用该封装函数即可实现GPU加速。

但实际处理过程中,由于数据量大或者希望使用CUDA流来实现CPU和GPU并行,从而最大限度提高运行效率,由于内存的创建和释放效率极低,上诉方式并不可取。而是在程序开始创建变量指针并分配内存,随后对该内存进行多次使用,在程序运行结束时再释放资源。这样之前的一个封装函数显然不够用,现在需要用户自己创建各项主机和设备端内存指针,然后分别提供相应封装函数以实现内存的创建、核心的GPU并行计算代码、内存释放若干操作。应用程序往往需要使用若干数据,而分别要在主机和设备端建立一个指针,因此造成函数参数数量过多的问题,我的一些程序中经常超过20个参数,这对程序的调用和管理造成极大的不便。

         为了解决这个问题,考虑把相关必要的参数封装在一个结构体里面,通过该结构体的指针进行传输传递,而封装函数通过该结构体获取必要的数据,从而函数的参数大大降低。而进一步,把结构体内部指针变量对应的内存创建、释放封装起来,用户只需要创建相应的结构体再调用这些封装函数即可,对代码的易用性和易维护性均有很大的提升。

         下面首先提供一个范例,这是根据cuda工程创建的范例进行的修改。

  1. #include “cuda_runtime.h”  
  2. #include “device_launch_parameters.h”  
  3.   
  4. #include <stdio.h>  
  5. #include <string.h>  
  6.   
  7. struct ProcPara  
  8. {  
  9.     int h_a; // host CPU  
  10.     int *h_b; // host CPU  
  11.     int *h_c; // host CPU  
  12.   
  13.     int *d_a; // device GPU  
  14.     int *d_b; // device GPU  
  15.     int *d_c; // device GPU  
  16.   
  17. };  
  18.   
  19. global void addKernel(ProcPara para)  
  20. {  
  21.     int i = threadIdx.x;  
  22.     para->d_c[i] = para->d_a[i] + para->d_b[i];  
  23. }  
  24.   
  25. void InitProcPara(ProcPara* para, int arraySize)  
  26. {  
  27. cudaMallocHost((void)&para->h_a, arraySize  sizeof(int));  
  28. cudaMallocHost((void)&para->h_b, arraySize  sizeof(int));  
  29. cudaMallocHost((void)&para->h_c, arraySize  sizeof(int));  
  30.   
  31.   
  32. cudaMalloc((void)&para->d_a, arraySize  sizeof(int));  
  33. cudaMalloc((void)&para->d_b, arraySize  sizeof(int));  
  34. cudaMalloc((void)&para->d_c, arraySize  sizeof(int));  
  35.   
  36. }  
  37.   
  38. void DeinitProcPara(ProcPara* para)  
  39. {  
  40.     // free host memory  
  41.     cudaFreeHost(para->h_a);  
  42.     cudaFreeHost(para->h_b);  
  43.     cudaFreeHost(para->h_c);  
  44.     // free device memory  
  45.     cudaFree(para->d_a);  
  46.     cudaFree(para->d_b);  
  47.     cudaFree(para->d_c);  
  48. }  
  49.   
  50. void addWithCuda(ProcPara* para, unsigned int arraySize)  
  51. {  
  52.     cudaSetDevice(0);  
  53.     cudaMemcpy(para->d_a, para->h_a, arraySize  sizeof(int), cudaMemcpyHostToDevice);  
  54.     cudaMemcpy(para->d_b, para->h_b, arraySize  sizeof(int), cudaMemcpyHostToDevice);  
  55.   
  56.     // Launch a kernel on the GPU with one thread for each element.  
  57.     addKernel << <1, arraySize >> >(para);  
  58.   
  59.     cudaMemcpy(para->h_c, para->d_c, arraySize  sizeof(int), cudaMemcpyDeviceToHost);  
  60.     cudaDeviceSynchronize();  
  61. }  
  62.   
  63.   
  64.   
  65.   
  66.   
  67. int main()  
  68. {  
  69.     const int arraySize = 5;  
  70.     const int a[arraySize] = { 1, 2, 3, 4, 5 };  
  71.     const int b[arraySize] = { 10, 20, 30, 40, 50 };  
  72.     int c[arraySize] = { 0 };  
  73.   
  74.     ProcPara para = new ProcPara;  
  75.   
  76.     InitProcPara(para, arraySize);  
  77.   
  78.   
  79.     memcpy(para->h_a, a, arraySize  sizeof(int));  
  80.     memcpy(para->h_b, b, arraySize  sizeof(int));  
  81.   
  82.     addWithCuda(para, arraySize);  
  83.   
  84.     memcpy(c, para->h_c, arraySize * sizeof(int));  
  85.   
  86.     printf(”{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n”,  
  87.         c[0], c[1], c[2], c[3], c[4]);  
  88.   
  89.     DeinitProcPara(para);  
  90.     delete para;  
  91.   
  92.     return 0;  
  93. }  
#include “cuda_runtime.h”

但是,这个程序并不能得到正确的结果。

这是因为,在传递给__global__函数的结构体指针的内存实体在主机内存中。而在非Unified Memory的条件下,GPU是不能访问到CPU端的内存数据的。由此,要继续使用结构体传递参数,需要进行如下的操作:

分别创建主机端和设备端结构体指针,并分配相应的内存。为主机端结构体内部成员变量分别分配主机端和设备端内存。然后将主机端结构体数据复制给设备端结构体。在核心计算代码中,主机端程序使用主机端结构体指针来获取所有数据的指针(包括数据拷贝),设备端程序使用设备端结构体指针获取设备端数据的指针。修改后的代码如下,这里需要注意的是,分配内存用到了指向指针的指针。因为指针本身是个变量,其保存的是个地址,分配内存要对该变量的内容进行更新,必须传递该变量的指针才有效,否则只是改变了参数变量的内容。这也是为什么cudaMalloc使用的是指针的指针的原因。

 

  1. #include “cuda_runtime.h”  
  2. #include “device_launch_parameters.h”  
  3.   
  4. #include <stdio.h>  
  5. #include <string.h>  
  6.   
  7. struct ProcPara  
  8. {  
  9.     int h_a; // host CPU  
  10.     int *h_b; // host CPU  
  11.     int *h_c; // host CPU  
  12.   
  13.     int *d_a; // device GPU  
  14.     int *d_b; // device GPU  
  15.     int *d_c; // device GPU  
  16.   
  17. };  
  18.   
  19. global void addKernel(ProcPara d_para)  
  20. {  
  21.     int i = threadIdx.x;  
  22.     d_para->d_c[i] = d_para->d_a[i] + d_para->d_b[i];  
  23.   
  24. }  
  25.   
  26. void InitProcPara(ProcPara**ha_para, ProcPara**da_para, int arraySize)  
  27. {  
  28.     //allocate stucture memory  
  29.     cudaMallocHost((void)ha_para, sizeof(ProcPara));  
  30.     cudaMalloc((void)da_para, sizeof(ProcPara));  
  31.   
  32.   
  33.     ProcPara*h_para = ha_para;  
  34.   
  35.     cudaMallocHost((void)&h_para->h_a, arraySize  sizeof(int));  
  36.     cudaMallocHost((void)&h_para->h_b, arraySize  sizeof(int));  
  37.     cudaMallocHost((void)&h_para->h_c, arraySize  sizeof(int));  
  38.   
  39.     cudaMalloc((void)&h_para->d_a, arraySize  sizeof(int));  
  40.     cudaMalloc((void)&h_para->d_b, arraySize  sizeof(int));  
  41.     cudaMalloc((void)&h_para->d_c, arraySize  sizeof(int));  
  42.   
  43.     // exchange data  
  44.     cudaMemcpy(*da_para, *ha_para, sizeof(ProcPara), cudaMemcpyHostToDevice);  
  45.   
  46. }  
  47.   
  48. void DeinitProcPara(ProcPara*h_para, ProcPara*d_para)  
  49. {  
  50.     // free host memory  
  51.     cudaFreeHost(h_para->h_a);  
  52.     cudaFreeHost(h_para->h_b);  
  53.     cudaFreeHost(h_para->h_c);  
  54.     // free device memory  
  55.     cudaFree(h_para->d_a);  
  56.     cudaFree(h_para->d_b);  
  57.     cudaFree(h_para->d_c);  
  58.   
  59.     //release stucture memory  
  60.     cudaFreeHost(h_para);  
  61.     cudaFree(d_para);  
  62.   
  63. }  
  64.   
  65. void addWithCuda(ProcPara h_para, ProcPara* d_para, unsigned int arraySize)  
  66. {  
  67.     cudaSetDevice(0);  
  68.   
  69.     cudaMemcpy(h_para->d_a, h_para->h_a, arraySize  sizeof(int), cudaMemcpyHostToDevice);  
  70.     cudaMemcpy(h_para->d_b, h_para->h_b, arraySize  sizeof(int), cudaMemcpyHostToDevice);  
  71.   
  72.     cudaDeviceSynchronize();  
  73.   
  74.     // Launch a kernel on the GPU with one thread for each element.  
  75.     addKernel << <1, 5 >> >(d_para);  
  76.   
  77.     cudaMemcpy(h_para->h_c, h_para->d_c, arraySize  sizeof(int), cudaMemcpyDeviceToHost);  
  78.     cudaDeviceSynchronize();  
  79.   
  80. }  
  81.   
  82.   
  83.   
  84. int main()  
  85. {  
  86.     const int arraySize = 5;  
  87.     const int a[arraySize] = { 1, 2, 3, 4, 5 };  
  88.     const int b[arraySize] = { 10, 20, 30, 40, 50 };  
  89.     int c[arraySize] = { 0 };  
  90.   
  91.     ProcPara *h_para;  
  92.     ProcPara *d_para;  
  93.   
  94.   
  95.     InitProcPara(&h_para, &d_para, arraySize);  
  96.   
  97.   
  98.     memcpy(h_para->h_a, a, arraySize  sizeof(int));  
  99.     memcpy(h_para->h_b, b, arraySize  sizeof(int));  
  100.   
  101.     addWithCuda(h_para, d_para, arraySize);  
  102.   
  103.     memcpy(c, h_para->h_c, arraySize  sizeof(int));  
  104.   
  105.     printf(”{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n”,c[0], c[1], c[2], c[3], c[4]);  
  106.   
  107.     DeinitProcPara(h_para, d_para);  
  108.   
  109.     return 0;  
  110. }  

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值