CUDA编程: GPU与CPU之间使用全局统一内存的完整代码及编译

2023-11-03

CUDA编程: GPU与CPU之间使用全局统一内存的完整代码及编译

最近碰到一个应用场景,需要从GPU访问host上创建的,一个很大的布隆过滤器(准确说是改进后的布谷鸟过滤器)。由于GPU卡上的显存有限,把整个过滤器复制到GPU卡显然不可能,于是想到用CUDA的全局统一内存来简化程序编写并提高性能。
由于以前没做过CUDA的编程,要从零开始学CUDA,还要进阶到用 统一虚拟内存寻址UVA,再到全局统一内存,甚至连CUDA的编译都是现学,碰到了不少问题。在参考这篇文章:
CPU与GPU的统一虚拟地址(CUDA UVA)原理
以及这篇文章:
CUDA全局内存
再到github上拉了一些源代码学习后,自己写了如一下完整的,使用统一内存,并可编译运行的简单例子,供正在入门CUDA编程的读者参考。
由于我只需要从GPU卡只读访问主机内存上的过滤器内容,因此这里除了演示cudaMallocManaged、cudaMemPrefetchAsync等与全局统一内存相关的函数用法外,还特别演示了cudaMemAdvise的用法。

	//通知GPU只需读取a、b数组
    cudaMemAdvise(a, size, cudaMemAdviseSetReadMostly, deviceId);
    cudaMemAdvise(b, size, cudaMemAdviseSetReadMostly, deviceId);

这里也存在一个有待还没进一步验证的疑问:对于我的应用,过滤器应该在host内存上创建,所以使用cudaMallocHost在主机上创建固定不可分页内存,是否比用cudaMallocManaged创建全局内存+cudaMemAdvise指定为只读但可分页内存的效率要来的更高?
示例程序(文件名: uva_test.cu)完整的源代码如下:

/**********************************************************************************************************************************
* 文件名 uva_test.cu
* 编译命令: nvcc -o test_uva uva_test.cu
* 一个测试CUDA的全局虚拟内存地址(UVA)的示范程序
* author: Ryan
***********************************************************************************************************************************/
#include <iostream>
#include <stdio.h>
#include <cuda_runtime_api.h>

using namespace std;

// --------------------------------------------------------------------------------------------------------------------------------
//计算GPU卡的SM数量
int _ConvertSMVer2Cores(int major,int minor) {

  // Defines for GPU Architecture types (using the SM version to determine
  // the # of cores per SM
  typedef struct {
    int SM;  // 0xMm (hexidecimal notation), M = SM Major version,
             // and m = SM minor version
    int Cores;
  } sSMtoCores;

  sSMtoCores nGpuArchCoresPerSM[] = {
    { 0x20, 32 }, // Fermi Generation (SM 2.0) GF100 class
    { 0x21, 48 }, // Fermi Generation (SM 2.1) GF10x class
    { 0x30, 192 },
    { 0x32, 192 },
    { 0x35, 192 },
    { 0x37, 192 },
    { 0x50, 128 },
    { 0x52, 128 },
    { 0x53, 128 },
    { 0x60,  64 },
    { 0x61, 128 },
    { 0x62, 128 },
    { 0x70,  64 },
    { 0x72,  64 },
    { 0x75,  64 },
    { 0x80,  64 },
    { 0x86, 128 },
    { -1, -1 } };

  int index = 0;

  while(nGpuArchCoresPerSM[index].SM != -1) {
    if(nGpuArchCoresPerSM[index].SM == ((major << 4) + minor)) {
      return nGpuArchCoresPerSM[index].Cores;
    }

    index++;
  }
  return 0;
}
// --------------------------------------------------------------------------------------------------------------------------------
//调用API显示GPU的硬件信息
void PrintCudaInfo() {
  cudaError_t err;
  const char *sComputeMode[] =
  {
    "Multiple host threads",
    "Only one host thread",
    "No host thread",
    "Multiple process threads",
    "Unknown",
    NULL
  };
  int deviceCount = 0;
  cudaError_t error_id = cudaGetDeviceCount(&deviceCount);
  if(error_id != cudaSuccess) {
    printf("GPUEngine: CudaGetDeviceCount %s\n",cudaGetErrorString(error_id));
    return;
  }
  // 如果本机未安装GPU卡,diviceCount的值将为0.
  if(deviceCount == 0) {
    printf("GPUEngine: There are no available device(s) that support CUDA\n");
    return;
  }
  //当前系统安装的驱动版本
  int driver_Version=0;
  cudaDriverGetVersion(&driver_Version);
  printf("[+] 系统当前共检测到[%d]块GPU卡,安装的CUDA驱动版本为:%d.%d\n",deviceCount,driver_Version / 1000, (driver_Version % 100) / 10);
  for(int i = 0; i<deviceCount; i++) {
    err = cudaSetDevice(i);
    if(err != cudaSuccess) {
      printf("[E] 错误,调用cudaSetDevice(%d)时发生错误: %s\n",i,cudaGetErrorString(err));
      return;
    }
    cudaDeviceProp deviceProp;
    cudaGetDeviceProperties(&deviceProp,i);
    //如果要得到准确的流处理器数量,需要判断deviceProp.major,再乘以
    printf("[+] 第[%d]块GPU卡[%s] (共有:%dx%d=%d个流处理核心,主频:%d MHz) (算力: %d.%d) (设备内存:%.2f MB) (%s)\n",
      i+1,deviceProp.name,deviceProp.multiProcessorCount, _ConvertSMVer2Cores(deviceProp.major,deviceProp.minor),
      deviceProp.multiProcessorCount*_ConvertSMVer2Cores(deviceProp.major,deviceProp.minor),deviceProp.clockRate/1000,
      deviceProp.major,deviceProp.minor,(double)deviceProp.totalGlobalMem / 1048576.0,
      sComputeMode[deviceProp.computeMode]);
    printf("[+] 第[%d]块GPU卡[%s] (maxGridSize[(%d,%d,%d)] (本设备%s统一虚拟寻址UVA)\n",i+1,deviceProp.name,deviceProp.maxGridSize[0],
    deviceProp.maxGridSize[1],deviceProp.maxGridSize[2],deviceProp.unifiedAddressing ? "支持":"不支持");
  }
}
// --------------------------------------------------------------------------------------------------------------------------------
void init_value(float num, float *a, int N)
{
  for(int i = 0; i < N; ++i)
  {
    a[i] = num;
  }
}
// --------------------------------------------------------------------------------------------------------------------------------
//检查计算结果
void checkElementsAre(float target, float *vector, int N)
{
  for(int i = 0; i < N; i++)
  {
    if(vector[i] != target)
    {
      printf("FAIL: vector[%d] - %0.0f does not equal %0.0f\n", i, vector[i], target);
      exit(1);
    }
  }
  printf("[+] 经检验,[%d]个结果计算均正确.\n",N);
}
// --------------------------------------------------------------------------------------------------------------------------------
//GPU的计算函数
__global__ void addVectorsInto(float *result, float *a, float *b, int N)
{
  int index = threadIdx.x + blockIdx.x * blockDim.x;
  int stride = blockDim.x * gridDim.x;

  for(int i = index; i < N; i += stride)
  {
    result[i] = a[i] + b[i];
  }
}
// --------------------------------------------------------------------------------------------------------------------------------
//main入口函数
int main(void){
    const int N = 2<<24;
    size_t size = N * sizeof(float);
    int count;
    int deviceId;
    int numberOfSMs;        //GPU的内核数量

    //查询当前可用GPU卡数量
    cudaGetDeviceCount(&count);
    if (count == 0) {
        fprintf(stderr, "[E] 错误,当前系统未检测到GPU卡.\n");
        return -1;
    }
    //显示GPU卡的硬件参数
    PrintCudaInfo();
    //获得第一个GPU设备号
    cudaGetDevice(&deviceId); 
    //获取GPU的内核数(注意仅是内核数,不是流处理器总数量)
    cudaDeviceGetAttribute(&numberOfSMs, cudaDevAttrMultiProcessorCount, deviceId);

    float *a;
    float *b;
    float *ret;
    
    printf("[+] 开始演示CUDA 的UVA全局虚拟内存功能,将创建[%.2f] KB的UVA内存变量\n",(double)((size*3)/(1024*1024.0)));
    //调用UVA全局虚拟内存申请函数
    cudaMallocManaged(&a, size);
    cudaMallocManaged(&b, size);
    //通知GPU只需读取a数组
    cudaMemAdvise(a, size, cudaMemAdviseSetReadMostly, deviceId);
    cudaMemAdvise(b, size, cudaMemAdviseSetReadMostly, deviceId);
    cudaMallocManaged(&ret, size);

    init_value(3, a, N);
    init_value(4, b, N);
    init_value(0, ret, N);

    // 调用cudaMemPrefetchAsync  将数据预取到GPU,对于a及b,由于已经用cudaMemAdvise指定为只读,
    // 将只在GPU产生只读地址副本,中途不检查生缺页中断,
    // 也仅在GPU端需要时,GPU才从host内存读入数据
    cudaMemPrefetchAsync(a, size, deviceId);
    cudaMemPrefetchAsync(b, size, deviceId);
    cudaMemPrefetchAsync(ret, size, deviceId);

    size_t threadsPerBlock;
    size_t numberOfBlocks;

    threadsPerBlock = 256;
    //GPU并行的线程数量
    numberOfBlocks = 32 * numberOfSMs;

    cudaError_t addVectorsErr;
    cudaError_t asyncErr;
    //调用GPU进行计算
    addVectorsInto<<<numberOfBlocks, threadsPerBlock>>>(ret, a, b, N);
    //取得GPU计算的结果状态
    addVectorsErr = cudaGetLastError();
    if(addVectorsErr != cudaSuccess) 
        printf("[E] GPU计算发生错误: %s\n", cudaGetErrorString(addVectorsErr));

    asyncErr = cudaDeviceSynchronize();
    if(asyncErr != cudaSuccess) 
        printf("[E] GPU计算发生错误: %s\n", cudaGetErrorString(asyncErr));

    //  将GPU计算完成后的数据刷新回到CPU
    cudaMemPrefetchAsync(ret, size, cudaCpuDeviceId);

    checkElementsAre(7, ret, N);
    //销毁cudaMallocManaged创建的内存
    cudaFree(a);
    cudaFree(b);
    cudaFree(ret);

    return 0;
}

上述代码,可以用以下命令编译为可执行文件:test_uva

$ nvcc -o test_uva uva_test.cu

在我的设备上运行的结果如下:

$ ./test_uva
[+] 系统当前共检测到[1]块GPU卡,安装的CUDA驱动版本为:11.7
[+][1]块GPU卡[NVIDIA GeForce RTX 3070 Ti] (共有:48x128=6144个流处理核心,主频:1770 MHz) (算力: 8.6) (设备内存:7981.00 MB) (Multiple host threads)
[+][1]块GPU卡[NVIDIA GeForce RTX 3070 Ti] (maxGridSize[(2147483647,65535,65535)] (本设备支持统一虚拟寻址UAV)
[+] 开始演示CUDA 的UVA全局虚拟内存功能,将创建[384.00] KB的UVA内存变量
[+] 经检验,[33554432]个结果计算均正确.

(全文完,作于2022-07-30)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CUDA编程: GPU与CPU之间使用全局统一内存的完整代码及编译 的相关文章

  • 无法使用 strptime() 获取秒数

    我收到 YYYY MM DDThh mm ss S Z hh mm 这种格式的日期时间 我正在尝试使用复制该值strptime如下所示 struct tm time 0 char pEnd strptime datetime Y m dT
  • UTF8/UTF16 和 Base64 在编码方面有什么区别

    In c 我们可以使用下面的类来进行编码 System Text Encoding UTF8 System Text Encoding UTF16 System Text Encoding ASCII 为什么没有System Text En
  • C++ 求二维数组每一行的最大值

    我已经设法用这个找到我的二维数组的每一行的最小值 void findLowest int A Cm int n int m int min A 0 0 for int i 0 i lt n i for int j 0 j lt m j if
  • 如何在没有 Control.Invoke() 的情况下从后台线程修改控件属性

    最近 我们遇到了一些旧版 WinForms 应用程序 我们需要更新一些新功能 在专家测试该应用程序时 发现一些旧功能被破坏 无效的跨线程操作 现在 在您认为我是新手之前 我确实有一些 Windows 窗体应用程序的经验 我不是专家 但我认为
  • FFMPEG Seeking 带来音频伪影

    我正在使用 ffmpeg 实现音频解码器 在读取音频甚至搜索已经可以工作时 我无法找到一种在搜索后清除缓冲区的方法 因此当应用程序在搜索后立即开始读取音频时 我没有任何工件 avcodec flush buffers似乎对内部缓冲区没有任何
  • 如何在我的应用程序中使用 Windows Key

    Like Windows Key E Opens a new Explorer Window And Windows Key R Displays the Run command 如何在应用程序的 KeyDown 事件中使用 Windows
  • 为什么禁止在 constexpr 函数中使用 goto?

    C 14 对你能做什么和不能做什么有规则constexpr功能 其中一些 没有asm 没有静态变量 看起来相当合理 但标准也不允许goto in constexpr功能 即使它允许其他控制流机制 这种区别背后的原因是什么 我以为我们已经过去
  • 当 Cortex-M3 出现硬故障时如何保留堆栈跟踪?

    使用以下设置 基于 Cortex M3 的 C gcc arm 交叉工具链 https launchpad net gcc arm embedded 使用 C 和 C FreeRtos 7 5 3 日食月神 Segger Jlink 与 J
  • 按字典顺序对整数数组进行排序 C++

    我想按字典顺序对一个大整数数组 例如 100 万个元素 进行排序 Example input 100 21 22 99 1 927 sorted 1 100 21 22 927 99 我用最简单的方法做到了 将所有数字转换为字符串 非常昂贵
  • .Net Core / 控制台应用程序 / 配置 / XML

    我第一次尝试使用新的 ConfigurationBuilder 和选项模式进入 Net Core 库 这里有很多很好的例子 https docs asp net en latest fundamentals configuration ht
  • 为什么模板不能位于外部“C”块内?

    这是一个后续问题一个答案 https stackoverflow com questions 4866433 is it possible to typedef a pointer to extern c function type wit
  • 在 ASP.Net Core 2.0 中导出到 Excel

    我曾经使用下面的代码在 ASP NET MVC 中将数据导出到 Excel Response AppendHeader content disposition attachment filename ExportedHtml xls Res
  • 是否有比 lex/flex 更好(更现代)的工具来生成 C++ 分词器?

    我最近将源文件解析添加到现有工具中 该工具从复杂的命令行参数生成输出文件 命令行参数变得如此复杂 以至于我们开始允许它们作为一个文件提供 该文件被解析为一个非常大的命令行 但语法仍然很尴尬 因此我添加了使用更合理的语法解析源文件的功能 我使
  • 我的 strlcpy 版本

    海湾合作委员会 4 4 4 c89 我的程序做了很多字符串处理 我不想使用 strncpy 因为它不会终止 我不能使用 strlcpy 因为它不可移植 只是几个问题 我怎样才能让我的函数正常运行 以确保它完全安全稳定 单元测试 这对于生产来
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • 在 URL 中发送之前对特殊字符进行百分比编码

    我需要传递特殊字符 如 等 Facebook Twitter 和此类社交网站的 URL 为此 我将这些字符替换为 URL 转义码 return valToEncode Replace 21 Replace 23 Replace 24 Rep
  • 已过时 - OpenCV 的错误模式

    我正在使用 OpenCV 1 进行一些图像处理 并且对 cvSetErrMode 函数 它是 CxCore 的一部分 感到困惑 OpenCV 具有三种错误模式 叶 调用错误处理程序后 程序终止 Parent 程序没有终止 但错误处理程序被调
  • 如何在 C# 中播放在线资源中的 .mp3 文件?

    我的问题与此非常相似question https stackoverflow com questions 7556672 mp3 play from stream on c sharp 我有音乐网址 网址如http site com aud
  • 更改显示的 DPI 缩放大小使 Qt 应用程序的字体大小渲染得更大

    我使用 Qt 创建了一些 GUI 应用程序 我的 GUI 应用程序包含按钮和单选按钮等控件 当我运行应用程序时 按钮内的按钮和字体看起来正常 当我将显示器的 DPI 缩放大小从 100 更改为 150 或 200 时 无论分辨率如何 控件的
  • 如何连接字符串和常量字符?

    我需要将 hello world 放入c中 我怎样才能做到这一点 string a hello const char b world const char C string a hello const char b world a b co

随机推荐