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

本文来源公众号“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 hostconst 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拷贝到GPUcudaMemcpy(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拷贝回CPUcudaMemcpy(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.mzph.cn/diannao/13871.shtml

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

相关文章

web压力测试,要不要过滤掉JS,CSS等请求?

在进行性能测试&#xff08;压测&#xff09;时&#xff0c;是否过滤掉对JavaScript、CSS等静态资源的请求&#xff0c;取决于你测试的目标和目的。 是测试服务端的性能还是前端的性能。这两种目的所涉及到的测试场景和工具等方法是不一样的。 一般的web产品&#xff0c;像cs…

java 8--Lambda表达式,Stream流

目录 Lambda表达式 Lambda表达式的由来 Lambda表达式简介 Lambda表达式的结构 Stream流 什么是Stream流&#xff1f; 什么是流呢&#xff1f; Stream流操作 中间操作 终端操作 Lambda表达式 Lambda表达式的由来 Java是面向对象语言&#xff0c;除了部分简单数据类型…

利用kubeadm安装k8s集群 以及跟harbor私有仓库下载镜像

目录 环境准备 master&#xff08;2C/4G&#xff09; 192.168.88.3 docker、kubeadm、kubelet、kubectl、flannel node01&#xff08;2C/2G&#xff09; 192.168.88.4 docker、kubeadm、kubelet、kubectl、flannel node02&#xff08;…

2024中青杯数学建模竞赛B题药物属性预测思路代码论文分享

2024年中青杯数学建模竞赛B题论文和代码已完成&#xff0c;代码为B题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#xff09;、模型…

QT调用Tinyxml2库解析XML结构文件

在学习SVG结构的时候&#xff0c;发现SVG结构可以通过以XML文件直接解析&#xff0c;所以就去了解了Tinyxml2库的使用&#xff0c;相关教程也比较多。 个人感觉Tinyxml2库比官方的XML解析库更好用&#xff0c;这里做个技术总结&#xff0c;记录Tinyxml2库解析XML文件结构的简单…

【Linux取经路】一个简单的日志模块

文章目录 一、可变参数的使用二、Log2.1 日志打印2.1.1 时间获取2.1.2 日志分块打印 2.2 打印模式选择2.3 Log 使用样例2.4 Log 完整源码 三、结语 一、可变参数的使用 int sum(int n, ...) {va_list s; // va_list 本质上就是一个指针va_start(s, n); int sum 0;while(n){su…

计算机毕业设计 | node.js(Express)+vue影院售票商城 电影放映购物系统(附源码+论文)

1&#xff0c;绪论 1.1 项目背景 最近几年&#xff0c;我国影院企业发展迅猛&#xff0c;各大电影院不断建设新的院线&#xff0c;每年新投入使用的荧幕数目逐年显著上升。这离不开人们的观影需求及对观影的过程要求的不断进步。广大观影消费者需要知道自己的空闲时间&#x…

Django中使用Celery(通用方案、官方方案)

Django中使用Celery&#xff08;通用方案、官方方案&#xff09; 目录 Django中使用Celery&#xff08;通用方案、官方方案&#xff09;通用方案场景前置准备完整代码 Celery官方方案【1】注册celery配置【2】创建celery文件【3】init注册【4】添加任务【5】启动worker异步任务…

设计模式六大原则之依赖倒置原则

文章目录 概念逻辑关系 小结 概念 依赖倒置原则指在设计代码架构时&#xff0c;高层模块不应该依赖底层模块&#xff0c;二者都应该依赖抽象。抽象不应该依赖于细节&#xff0c;细节应该依赖于抽象。 逻辑关系 如上图所示&#xff0c;逻辑应该就是这样&#xff0c;高层依赖于…

解决Wordpress中Cravatar头像无法访问问题

一、什么是Cravatar Gravatar是WordPress母公司Automattic推出的一个公共头像服务&#xff0c;也是WordPress默认的头像服务。但因为长城防火墙的存在&#xff0c;Gravatar在中国时不时就会被墙一下&#xff0c;比如本次从2021年2月一直到8月都是不可访问状态。 在以往的时候&…

R语言:单细胞pcoa降维和去批次

#生成随机颜色 > randomColor <- function() { paste0("#",paste0(sample(c(0:9, letters[1:6]), 6, replace TRUE),collapse "")) } # 生成100个随机颜色 > randomColors <- replicate(100,randomColor()) > seuratreadRDS("seu…

前端javascript包管理,npm升级用pnpm

一 pnpm 介绍 pnpm&#xff08;Package Manager&#xff09;是一个快速、节省磁盘空间的 JavaScript 包管理器&#xff0c;它是 Node.js 生态系统中 npm 的一个替代品。pnpm 解决了传统包管理工具在处理依赖时的一些痛点&#xff0c;特别是关于存储空间使用和依赖地狱的问题。…

如何将Google Search Console添加到WordPress和GA4

您想知道如何将 Google Search Console 添加到您的 Google Analytics 帐户和 WordPress 网站吗&#xff1f; 作为网站主&#xff0c;Google Search Console 是一款不能不使用的工具。对于任何想要确保其网站在 Google 搜索结果中表现良好的人来说&#xff0c;这绝对是一个必不…

leetCode-hot100-数组专题之区间问题

数组专题之区间问题 知识点&#xff1a;解决思路&#xff1a;例题56.合并区间57.插入区间253.会议室 Ⅱ485.无重叠区间 数组区间问题是算法中常见的一类问题&#xff0c;它们通常涉及对数组中的区间进行排序、合并、插入或删除操作。无论是合并区间、插入区间还是删除重复空间&…

【HarmonyOS尝鲜课】- 下载、安装DevEco Studio以及配置环境、创建运行HarmonyOS项目

下载、安装开发工具 进入DevEco Studio下载官网&#xff0c;单击“立即下载”进入下载页面。 这里以Windows为例进行安装&#xff0c;可以根据操作系统选择对应的版本进行下载。 下载完成后解压一下&#xff0c;进入文件里&#xff0c;双击应用程序&#xff0c;打开安装向导&a…

Redis主从、哨兵、集群讲解

一、Redis主从 大家在面试中可能经常会被问到Redis的高可用问题。Redis高可用回答包括两个层面&#xff0c;一个就是数据不能丢失&#xff0c;或者说尽量减少丢失 ;另外一个就是保证Redis服务不中断 。 对于尽量减少数据丢失&#xff0c;可以通过AOF和RDB保证。 对于保证服务…

linux---线程控制

线程和进程 以前我们要同时跑多个程序&#xff0c;可以通过fork()多个子进程&#xff0c;然后通过系统函数进行程序的替换&#xff0c;但是创建进程代价大&#xff0c;不仅要拷贝一份父进程的地址空间&#xff0c;页表&#xff0c;文件表述符表等。但是线程不需要因为是进程的…

windows docker desktop 更换镜像存储目录

windows docker desktop 更换镜像存储目录 方法&#xff1a;如图&#xff0c;Browse浏览一个新的目录并选中&#xff0c;确定后&#xff0c;程序会开始stop&#xff0c;在stop完成前&#xff0c;会持续迁移原有镜像到新的位置&#xff0c;你会发现目标位置的磁盘占用空间越来越…

Mac网线连接windows本【局域网互传文件】

Mac网线连接windows本【局域网互传文件】 两台电脑网线互联 Mac->网络->USP TCP/IP 手动配置IP&#xff0c;子网掩码&#xff0c;路由器 windows 网络和Internet配置->更改适配器选项->以太网->Internet协议版本4&#xff08;TCP/IPv4&#xff09;->属性 …

K8S/ hpa分享

在 Kubernetes 中&#xff0c;HorizontalPodAutoscaler 自动更新工作负载资源 &#xff08;例如 Deployment 或者 StatefulSet&#xff09;&#xff0c; 目的是自动扩缩工作负载以满足需求。 hpa的使用本身还是很简单的 示例如下&#xff1a; 官网示例 apiVersion: apps/v1 k…