DeepDriving | CUDA编程-02: 初识CUDA编程

news2024/11/17 7:19:55

本文来源公众号“DeepDriving”,仅用于学术分享,侵权删,干货满满。

原文链接:CUDA编程-02: 初识CUDA编程

上一篇文章DeepDriving | CUDA编程-01: 搭建CUDA编程环境-CSDN博客介绍了如何搭建CUDA编程环境,从这篇文章开始正式开始介绍如何使用CUDA进行编程。

1 异构计算架构

如下图所示,一个典型的异构计算架构节点由一个多核CPU和一个或多个GPU组成,每个CPUGPU都是独立的设备,它们之间通过PCIe总线连接。GPU作为CPU的协处理器用于执行一些并行计算任务。CPU适合用于做一些逻辑控制任务,而GPU则适合作为CPU的协处理器用于做一些大计算量的数据并行计算任务。

heterogeneous_architecture

一般我们称CPUhost,称GPUdevice,相应地,一个异构计算的应用程序代码也被分为两部分:运行在CPU上的程序被称为host代码,运行在GPU上的程序被称为device代码。

heterogeneous_architecture

2 一个Hello World

在我们学习一种新的编程语言的时候,一般都是先从打印一句"Hello World"开始,今天开始学习CUDA编程,按照国际惯例,也从打印"Hello World"开始。

首先新建一个CUDA C源文件hello_world.cu,然后输入下面的内容:

#include <stdio.h>

int main(void)
{
    printf("Hello World from CPU\n");
    return 0;
}

nvcc进行编译生成可执行文件:

nvcc hello_world.cu -o hello_world

运行可执行文件hello_world,没有意外的话就能在终端看到打印出的"Hello World from CPU"了。这是在CPU上运行代码打印了这句话,那么怎么在GPU上打印呢?

要在GPU上运行程序,我们需要写一个能在GPU上执行的函数,然后在CPU上调用这个函数。来看一个例子:

#include <stdio.h>

__global__ void HelloFromGPU(void)
{
    printf("Hello from GPU\n");
}

int main(void)
{
    printf("Hello from CPU\n");
    HelloFromGPU<<<1, 5>>>();
    cudaDeviceReset();
    return 0;
}

在上面的这段代码里,我添加了一个用__global__函数类型限定符修饰的函数HelloFromGPU,然后在main函数中去调用它。在CUDA中,有以下3种函数类型限定符:

_global_

__global__函数类型限定符修饰的函数被称为内核函数,该函数在host上被调用,在device上执行,只能返回void类型,不能作为类的成员函数。调用__global__修饰的函数是异步的,也就是说它未执行完就会返回。

_device_

__device__函数类型限定符修饰的函数只能在device上被调用,在device上执行,用于在device代码中内部调用。

_host_

__host__函数类型限定符修饰的函数只能在host上被调用,在host上执行,也就是host上的函数,__host__函数类型限定符可以省略。

与调用一般CPU函数不同的是,调用HelloFromGPU函数的时候会在后面写上<<< >>>,意思这是从host端到device端的内核函数调用,里面的参数是执行配置,用来说明使用多少线程来执行内核函数。一个内核函数是通过一组线程来执行的,所有线程执行的同样的代码,我这里设置的是用了5个线程来执行。程序编译成功后运行得到如下结果:

Hello from CPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU
Hello from GPU

可以看到,内核函数里的打印语句被执行了5次。

3 用CUDA实现数组相加

一个典型的CUDA程序结构一般由以下5个步骤组成:

  1. 分配GPU内存;

  2. CPU内存中拷贝数据到GPU内存中;

  3. 调用CUDA内核函数执行程序指定的计算任务;

  4. GPU内存中把数据拷贝回CPU内存中;

  5. 释放GPU内存;

下面以一个数组相加的例子来展示以上过程,数组相加的计算过程比较简单:数组a和数组b中对应下标的元素相加然后存入数组c中。

vector_add

首先来看一下在CPU上实现数组相加的代码:

#include <iostream>

void VectorAddCPU(const float *const a, const float *const b, float *const c,
                  const int n) {
  for (int i = 0; i < n; ++i) {
    c[i] = a[i] + b[i];
  }
}

int main(void) {
  // alloc memory for host
  const size_t size = 1024;
  float *ha = new float[size]();
  float *hb = new float[size]();
  float *hc = new float[size]();

  for (int i = 0; i < size; ++i) {
    ha[i] = i;
    hb[i] = size - i;
  }

  VectorAddCPU(ha, hb, hc, size);

  delete[] ha;
  delete[] hb;
  delete[] hc;

  return 0;
}

函数VectorAddCPU用了一个for循环在CPU上实现数组相加的过程。如果要用GPU来实现该过程,则调用CUDAAPI按照前面说的5个步骤编写代码:

#include <cuda_runtime.h>
#include <iostream>

__global__ void VectorAddGPU(const float *const a, const float *const b,
                             float *const c, const int n) {
  int i = blockDim.x * blockIdx.x + threadIdx.x;
  if (i < n) {
    c[i] = a[i] + b[i];
  }
}

int main(void) {
  // 分配CPU内存
  const size_t size = 1024;
  float *ha = new float[size]();
  float *hb = new float[size]();
  float *hc = new float[size]();

  for (int i = 0; i < size; ++i) {
    ha[i] = i;
    hb[i] = size - i;
  }

  // 分配GPU内存
  float *da = nullptr;
  float *db = nullptr;
  float *dc = nullptr;
  cudaMalloc((void **)&da, size);
  cudaMalloc((void **)&db, size);
  cudaMalloc((void **)&dc, size);

  // 把数据从CPU拷贝到GPU
  cudaMemcpy(da, ha, size, cudaMemcpyHostToDevice);
  cudaMemcpy(db, hb, size, cudaMemcpyHostToDevice);
  cudaMemcpy(dc, hc, size, cudaMemcpyHostToDevice);

  const int thread_per_block = 256;
  const int block_per_grid = (size + thread_per_block - 1) / thread_per_block;

  // 调用核函数
  VectorAddGPU<<<block_per_grid, thread_per_block>>>(da, db, dc, size);

  // 把数据从GPU拷贝回CPU
  cudaMemcpy(hc, dc, size, cudaMemcpyDeviceToHost);

  // 释放GPU内存
  cudaFree(da);
  cudaFree(db);
  cudaFree(dc);

  // 释放CPU内存
  delete[] ha;
  delete[] hb;
  delete[] hc;

  return 0;
}

在这段代码里,用到了几个CUDA的内存管理函数:

cudaMalloc

函数原型:

cudaError_t cudaMalloc(void** devPtr, size_t size)

该函数用于在GPU上分配指定大小的内存空间,类似于标准C语言中的malloc函数。

cudaMemcpy

函数原型:

cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)

该函数用于内存拷贝,类似于标准C语言中的memcpy函数。数据拷贝的流向由参数kind指定,分为以下4种方式:

  • cudaMemcpyHostToHost

  • cudaMemcpyHostToDevice

  • cudaMemcpyDeviceToHost

  • cudaMemcpyDeviceToDevice

从它们的字面意思就能知道,如果是从host拷贝数据到device,那么kind参数应该设置为cudaMemcpyHostToDevice;如果是从device拷贝数据到host,那么kind参数应该设置为cudaMemcpyDeviceToHost。上面的代码体现了这一点。

cudaFree

udaError_t cudaFree(void* devPtr)

该函数用于释放已分配的GPU内存空间,类似于标准C语言中的free函数。

在上面的代码中,内核函数VectorAddGPU用于实现数组相加,与前面的代码中VectorAddCPU函数不同的是,VectorAddGPU函数里面并没有用for循环,因为在GPU中是将数据进行并行化划分,然后通过线程组去实现计算过程的,线程组中的每个线程都是执行c[i] = a[i] + b[i]这个计算过程。在这个程序里,数组的长度为1024,我设置了每个线程组中的线程数量为256,总共用了4个线程组去进行计算。

关于GPU中线程和线程组的相关知识,我将在下一篇文章中进行阐述。

4 参考资料

  • Professional CUDA C Programming

  • CUDA C Programming_Guide

THE END !

文章结束,感谢阅读。您的点赞,收藏,评论是我继续更新的动力。大家有推荐的公众号可以评论区留言,共同学习,一起进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1684267.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

阿里云数据库 SelectDB 版全面商业化,开启现代化实时数据仓库的全新篇章

2024 年 5 月 21 日&#xff0c;由阿里云联合飞轮科技共同举办的「阿里云数据库 SelectDB 版商业化产品发布会」于线上召开。阿里巴巴集团副总裁、阿里云数据库产品事业部负责人李飞飞宣布&#xff0c;阿里云数据库 SelectDB 版在中国站及国际站全面发布&#xff0c;正式开启商…

5. JVM面试题汇总

Java全栈面试题汇总目录-CSDN博客 1. 说一下JVM的主要组成部分及其作用? JVM包含两个子系统和两个组件&#xff0c;两个子系统为Class loader(类装载)、Execution engine(执行引擎)&#xff1b;两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。 Cl…

Kibanna安装配置

环境&#xff1a;windows10、ES&#xff08;8.13.3&#xff09;、Kibana&#xff08;8.13.3&#xff09;、Logstash&#xff08;8.13.3&#xff09; 1.Kibanna安装配置 Kibanna对ES的数据进行可视化、分析和监控 Download Kibana Free | Get Started Now | ElasticDownload K…

零部件销售|基于SSM+vue的轻型卡车零部件销售平台系统的设计与实现(源码+数据库+文档)

轻型卡车零部件销售平台 目录 基于SSM&#xff0b;vue的轻型卡车零部件销售平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1 系统功能模块 2 管理员功能模块 3 用户后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题…

MavLinK协议

由于在公司需要使用这个&#xff0c;我就写一个文章用于入门级别 简单介绍 MAVSDK是PX4开源团队贡献的基于mavlink通信协议的用于无人机应用开发的SDK&#xff0c;其可以部署在Windows、Linux、Android等多种平台&#xff0c;并且支持多种语言如c/c、python、Java等。 在官网…

5月30日在线研讨会 | 面向智能网联汽车的产教融合解决方案

随着智能网联汽车技术的快速发展&#xff0c;产业对高素质技术技能人才的需求日益增长。为了促进智能网联汽车行业的健康发展&#xff0c;推动教育链、人才链与产业链、创新链的深度融合&#xff0c;经纬恒润推出产教融合相关方案&#xff0c;旨在通过促进教育链与产业链的深度…

XV4001KD汽车级应用的数字输出陀螺传感器

XV4001KD是一款专为汽车导航系统和远程信息处理而设计的数字输出陀螺传感器。采用SPI/I2C串行接口&#xff0c;具有高精度的16位的角速率输出和11位的温度输出功能&#xff0c;能够准确地测量车辆的运动状态和环境温度&#xff0c;为导航系统和信息处理提供可靠的数据支持。以及…

深度学习基础之《TensorFlow框架(18)—卷积神经网络(2)》

一、卷积层 1、卷积层&#xff08;Convolutional Layer&#xff09;介绍 卷积神经网络中每层卷积层由若干卷积单元&#xff08;卷积核&#xff09;组成&#xff0c;每个卷积单元的参数都是通过反向传播算法最佳化得到的 卷积运算的目的是特征提取&#xff0c;第一层卷积层可能…

word页眉线如何置于文字上方

然后 敲黑板&#xff0c;点这里

学硕都考11408的211院校!河北工业大学计算机考研考情分析!

河北工业大学&#xff08;Hebei University of Technology&#xff09;&#xff0c;简称河北工大&#xff0c;坐落于天津市&#xff0c;由河北省人民政府、天津市人民政府与中华人民共和国教育部共建&#xff0c; 隶属于河北省&#xff0c;是国家“双一流”建设高校、国家“211…

Linux磁盘高级操作

RAID RAID存储系统是一种数据存储虚拟化技术&#xff0c;它将多个物理磁盘驱动器组合成一个或多个逻辑单元&#xff0c;以提供数据冗余和/或提高性能。 1. RAID 0 无奇偶校验与冗余&#xff08;磁盘容错&#xff09;的条带存储&#xff08;带区卷/条带卷&#xff09; 由两块…

科技巨头的下一个目标:Web3与物联网融合

引言 随着技术的不断演进和创新&#xff0c;科技巨头们正在将目光聚焦到Web3与物联网的融合领域&#xff0c;这将开启一个全新的数字时代。本文将深入探讨科技巨头们在这一领域的动向&#xff0c;以及融合可能带来的影响和未来发展方向。 Web3与物联网的融合趋势 技术发展的趋…

【手写大跟堆详解】

文章目录 大跟堆介绍大跟堆的结构大跟堆的应用场景大跟堆的代码实现 大跟堆介绍 大根堆&#xff08;Max Heap&#xff09;是一种特殊的二叉树结构&#xff0c;它满足以下两个条件&#xff1a; 1.完全二叉树&#xff1a;大根堆是一棵完全二叉树&#xff0c;即除了最后一层外&am…

Web Server项目实战4-服务器编程基本框架和2种高效的事件处理模式

服务器编程基本框架 虽然服务器程序种类繁多&#xff0c;但其基本框架都一样&#xff0c;不同之处在于逻辑处理 模块功能I/O处理单元处理客户连接&#xff0c;读写网络数据逻辑单元业务进程或线程网络存储单元数据库、文件或缓存请求队列各单元之间的通信方式 I/O 处理单元是…

520主题趣味小游戏玩法线上互动的作用是什么

行业商家借势520气氛&#xff0c;往往能低成本达到预期效果&#xff0c;包括但不限于品牌传播、渠道引流涨粉、用户促活引导等&#xff0c;除了前面推荐的互动玩法外&#xff0c;在【雨科】平台的这几款520趣味小游戏同样值得关注。 1、爱你不止520 这是一款九宫格抽奖活动&am…

【Java面试】三、Redis篇(下)

文章目录 1、抢券场景2、Redis分布式锁3、Redisson实现分布式锁4、Redisson实现的分布式锁是可重入锁5、Redisson实现分布式锁下的主从一致性6、面试 1、抢券场景 正常思路&#xff1a; 代码实现&#xff1a; 比如优惠券数量为1。正常情况下&#xff1a;用户A的请求过来&a…

利用神经网络学习语言(六)——总结与常见面试问题

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 文章列表&#xff1a; 利用神经网络学习语言&#xff08;一&#xff09;——自然语言处理的基本要素利用神经网络学习语言&…

基于STM32看Cortex-M内核相关的一些底层知识

文章目录 固件起始地址存储了主栈指针和向量表内容启动文件分析程序启动流程Code,RO Data,RW Data, ZI Data启动流程Regin$$Table 固件起始地址存储了主栈指针和向量表内容 《ARM Cortex-M3与Cortex-M4权威指南》中的4.8章节复位和复位流程中有下面这段的描述&#xff1a; 在复…

医疗软件供应链安全治理:保障医疗服务质量和患者安全的当务之急!

如今&#xff0c;随着医疗数智化的不断深入&#xff0c;医共体网络、远程医疗网络、区域医疗网络、互联网医院等系统建设日益普及&#xff0c;医疗信息系统从基础应用进阶到智能医疗阶段。医疗机构对医疗软件采购、外包开发以及调用第三方开发资源的需求日益增加。 然而&#x…

buuctf的RSA(二)

1.RSA 知道 flag.enc 和 pub.key&#xff0c;典型的加密、解密 将pub,key 改为pub.txt 打开后发现公钥 在RSA公私钥分解 Exponent、Modulus&#xff0c;Rsa公私钥指数、系数(模数)分解--查错网 进行解密 得到e65537 n8693448229604811919066606200349480058890565…