cuda编码例程(转载借鉴)

news2024/11/20 8:38:58

内容出处:https://mp.csdn.net/mp_blog/creation/editor

1. 前言

这是一份简单的CUDA编程入门,主要参考英伟达的官方文档进行学习,本人也是刚开始学习,如有表述错误,还请指出。官方文档链接如下:

An Even Easier Introduction to CUDA | NVIDIA Technical Blog​

developer.nvidia.com/blog/even-easier-introduction-cuda/

本文先从一份简单的C++代码开始,然后逐步介绍如何将C++代码转换为CUDA代码,以及对转换前后程序的运行时间进行对比

本文所使用的CPU为i7-4790,GPU为GTX 1080,那就开始吧。

2. 一份简单的C++代码

首先是一份简单的C++代码,主要的运行函数为add函数,该函数实现功能为30M次的for循环,每次循环进行一次加法。

// add.cpp
#include <iostream>
#include <math.h>
#include <sys/time.h>

// function to add the elements of two arrays
void add(int n, float *x, float *y)
{
  for (int i = 0; i < n; i++)
      y[i] = x[i] + y[i];
}

int main(void)
{
  int N = 1<<25; // 30M elements

  float *x = new float[N];
  float *y = new float[N];

  // initialize x and y arrays on the host
  for (int i = 0; i < N; i++) {
    x[i] = 1.0f;
    y[i] = 2.0f;
  }

  struct timeval t1,t2;
  double timeuse;
  gettimeofday(&t1,NULL);
  // Run kernel on 30M elements on the CPU
  add(N, x, y);
  gettimeofday(&t2,NULL);
  timeuse = (t2.tv_sec - t1.tv_sec) + (double)(t2.tv_usec - t1.tv_usec)/1000.0;

  std::cout << "add(int, float*, float*) time: " << timeuse << "ms" << std::endl;
  // Check for errors (all values should be 3.0f)
  float maxError = 0.0f;
  for (int i = 0; i < N; i++)
    maxError = fmax(maxError, fabs(y[i]-3.0f));
  std::cout << "Max error: " << maxError << std::endl;

  // Free memory
  delete [] x;
  delete [] y;

  return 0;
}

编译以及运行代码:

g++ add.cpp -o add
./add

不出意外的话,你应该得到下面的结果:

第一行表示add函数的运行时间,第二行表示每个for循环里的计算是否与预期结果一致。

这个简单的C++代码在CPU端运行,运行时间为85ms,接下来介绍如何将主要运算的add函数迁移至GPU端。

3. 把C++代码改成CUDA代码

将C++代码改为CUDA代码,目的是将add函数的计算过程迁移至GPU端,利用GPU的并行性加速运算,需要修改的地方主要有3处:

  1. 首先需要做的是将add函数变为GPU可运行函数,在CUDA中称为kernel,为此,仅需将变量声明符添加到函数中,告诉 CUDA C++ 编译器这是一个在 GPU 上运行并且可以从 CPU 代码中调用的函数。
__global__ 
void add(int n, float *x, float *y)
{
  for (int i = 0; i < n; i++)
    y[i] = x[i] + y[i];
}

那么修改后的add函数的调用也比较简单,仅需要在add函数名后面加上三角括号语法

指定CUDA内核启动即可,称为执行配置(execution configuration),用于配置程序运行时的线程,后续会讲到,目前先将其设置为

add<<<1, 1>>>(N, x, y);

2. 那么为了在GPU进行计算,需要在GPU上分配可访问的内存。CUDA中通过Unified Memory(统一内存)机制来提供可同时供GPU和CPU访问的内存,使用cudaMallocManaged()函数进行分配:

cudaMallocManaged(&x, N*sizeof(float));
cudaMallocManaged(&y, N*sizeof(float));

同时,在程序最后使用cudaFree()进行内存释放:

cudaFree(x);
cudaFree(y);

其实就相当于C++中的new跟delete。

3. add函数在GPU端运行之后,CPU需要等待cuda上的代码运行完毕,才能对数据进行读取,因为CUDA内核启动时并未对CPU的线程进行固定,需要使用cudaDeviceSynchronize()函数进行同步。

4. 整体的程序如下所示:

// add.cu
#include <iostream>
#include <math.h>
// Kernel function to add the elements of two arrays
// __global__ 变量声明符,作用是将add函数变成可以在GPU上运行的函数
// __global__ 函数被称为kernel,
// 在 GPU 上运行的代码通常称为设备代码(device code),而在 CPU 上运行的代码是主机代码(host code)。
__global__ 
void add(int n, float *x, float *y)
{
  for (int i = 0; i < n; i++)
    y[i] = x[i] + y[i];
}

int main(void)
{
  int N = 1<<25;
  float *x, *y;

  // Allocate Unified Memory – accessible from CPU or GPU
  // 内存分配,在GPU或者CPU上统一分配内存
  cudaMallocManaged(&x, N*sizeof(float));
  cudaMallocManaged(&y, N*sizeof(float));

  // initialize x and y arrays on the host
  for (int i = 0; i < N; i++) {
    x[i] = 1.0f;
    y[i] = 2.0f;
  }

  // Run kernel on 1M elements on the GPU
  // execution configuration, 执行配置
  add<<<1, 1>>>(N, x, y);

  // Wait for GPU to finish before accessing on host
  // CPU需要等待cuda上的代码运行完毕,才能对数据进行读取
  cudaDeviceSynchronize();

  // Check for errors (all values should be 3.0f)
  float maxError = 0.0f;
  for (int i = 0; i < N; i++)
    maxError = fmax(maxError, fabs(y[i]-3.0f));
  std::cout << "Max error: " << maxError << std::endl;

  // Free memory
  cudaFree(x);
  cudaFree(y);
  
  return 0;
}

使用nvcc对程序进行编译并运行:

nvcc add.cu -o add_cuda 
./add_cuda

或者使用nvprof进行速度测试:

nvprof ./add_cuda

不出意外的话,你会得到以下输出:

框出来的就是add函数在GPU端的运行时间,为4s。没错,就是比CPU端85ms还要慢,那还学个锤子。

4. 使用CUDA代码并行运算

好的回过头看看,问题出现在这个执行配置 <<<i,j>>> 上。不急,先看一下一个简单的GPU结构示意图,按照层次从大到小可将GPU按照 grid -> block -> thread划分,其中最小单元是thread,并行的本质就是将程序的计算模块拆分成多个小模块扔给每个thread并行计算。

再看一下前面执行配置 `<<<i,j>>>` 的含义,`<<<i,j>>>` 应该写成 `<<<numBlocks, blockSize>>>` ,即表示函数运行时使用的block数量以及每个block的大小,前面我们将其设置为`<<<1,1>>>` ,说明程序是单线程运行的,那当然慢了~~。下面我们以单个block为例,将其改为`<<<1,256>>>`,add函数也需要适当修改:

__global__
void add(int n, float *x, float *y)
{
  int index = threadIdx.x; // threadIdx.x表示当前在第几个thread上运行
  int stride = blockDim.x; // blockDim.x表示每个block的大小
  for (int i = index; i < n; i += stride)
      y[i] = x[i] + y[i];
}

修改的部分也比较好理解,不赘述了,接下来运行看看结果:

你看,开始加速了吧,4s加速到了77ms。

那么,`<<<numBlocks, blockSize>>>` 的两个参数应该怎么设置好呢。首先,CUDA GPU 使用大小为 32 的倍数的线程块运行内核,因此 `blockSize` 的大小应该设置为32的倍数,例如128、256、512等。确定 `blockSize` 之后,可以根据for循环的总个数`N`确定 `numBlock` 的大小(注意四舍五入的误差):

int numBlock = (N + blockSize - 1) / blockSize;

当然因为变成了多个`block`,所以此时add函数需要再改一下:

__global__ 
void add(int n, float *x, float *y)
{
  int index = blockIdx.x * blockDim.x + threadIdx.x;
  int stride = blockDim.x * gridDim.x;
  for (int i = index; i < n; i+=stride)
    y[i] = x[i] + y[i];
}

这里index跟stride的计算可以参考上面GPU结构图以及下面的图(图取自An Even Easier Introduction to CUDA | NVIDIA Technical Blog),自行推算,较好理解。

搞定之后再编译运行一下:

看看,又加速了不是,通过提升并行度而加速,相比于CPU端(85ms)加速了接近一倍左右。

5. 结论

以上仅是一份简单的CUDA入门代码,看起来还算比较简单,不过继续深入肯定有更多的坑,期待后面有时间继续学习。

本文代码

[1] GitHub - xcyuyuyu/My-First-CUDA-Code: The introduction to cuda, a simple and easy cuda project

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

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

相关文章

第2章-类加载子系统

1、本系列博客&#xff0c;主要是面向Java8的虚拟机。如有特殊说明&#xff0c;会进行标注。 2、本系列博客主要参考尚硅谷的JVM视频教程&#xff0c;整理不易&#xff0c;所以图片打上了一些水印&#xff0c;还请读者见谅。后续可能会加上一些补充的东西。 3、尚硅谷的有些视频…

vue 实现el-select组件 配合 el-tabs 完成动态tabs然后有勾选 全选,还有模拟提交,回显数据

cv即可使用 <!DOCTYPE html> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" conten…

Python读取DataFrame的某行或某列

行索引、列索引、loc和iloc import pandas as pd import numpy as np # 准备数据 df pd.DataFrame(np.arange(12).reshape(3,4),indexlist("abc"),columnslist("WXYZ"))行索引(index)&#xff1a;对应最左边那一竖列 列索引(columns)&#xff1a;对应最…

使用手机在网状态查询 API 有效防止虚假注册的设计思路

引言 随着移动互联网的普及&#xff0c;手机在网状态成为重要的数据指标。在网状态反映了手机用户的实际使用情况&#xff0c;对于各类企业和机构具有重要意义。 本文将为大家介绍手机在网状态 API 的主要特点和优势&#xff0c;并且探讨手机在网状态 API 的应用场景和效果展…

【小DS】ABC250 E - Prefix Equality

一开始看题解把我CPU干烧了 后来豁然开朗 E - Prefix Equality (atcoder.jp) 题意&#xff1a; 给定两个数组a,b&#xff0c;每次询问两个位置x和y&#xff0c;问a数组前x个构成的集合和b数组前y个构成的集合是不是一样 思路&#xff1a; 一开始纯暴力RE了 #include <…

IDEA 重磅插件 - Bito – GPT-4

笔者会陆续在个人主页 “AI” 专栏推荐优质 AI 软件、插件、网站… 而不是一股脑地抛给你一堆自行筛选&#xff0c;每一款都是笔者亲自体验感觉还不错的。 如果对你有帮助记得一键三连获取最新优质文章&#xff01; 1.介绍 Bito – GPT-4 Bito – GPT-4 & ChatGPT to writ…

WiFi 时钟

WiFi 时钟有很多开源项目的。但是&#xff0c;成品往往代码一大篇&#xff0c;看起来有些上头。加上有些库和环境的版本变迁&#xff0c;编译报错排查起来很是费劲。于是从头捋一遍&#xff0c;一步一步的过程&#xff0c;容易上手&#xff1a; 准备工作&#xff1a; a 零件&…

Netty源码解读

Netty源码解读 Netty线程模型 1、定义了两组线程池BossGroup和WorkerGroup&#xff0c;BossGroup专门负责接收客户端的连接, WorkerGroup专门负责网络的读写 2、BossGroup和WorkerGroup类型都是NioEventLoopGroup&#xff0c;Group中维护了多个事件循环线程NioEventLoop&#…

AI新产品层出不穷,学不过来怎么办。

最近各个互联网巨头和创业新贵发布的AI工具&#xff0c;AI模型层出不穷&#xff0c;相关自媒体的热度也都很高&#xff0c;当然&#xff0c;各种大佬的隔空喊话也是非常吸引眼球&#xff0c;那么很多人就会觉得&#xff0c;要看的东西太多了&#xff0c;要学的东西太多了&#…

【数据结构】顺序表详解(附leetcode练习题)

☃️个人主页&#xff1a;fighting小泽 &#x1f338;作者简介&#xff1a;目前正在学习C语言和数据结构 &#x1f33c;博客专栏&#xff1a;数据结构 &#x1f3f5;️欢迎关注&#xff1a;评论&#x1f44a;&#x1f3fb;点赞&#x1f44d;&#x1f3fb;留言&#x1f4aa;&…

Java编译器插件Manifold(流形)

流形 文天祥正气歌中有云&#xff1a;“天地有正气&#xff0c;杂然赋流形”。 流形是一种抽象而又具体的事务&#xff0c;要研究一个事务就要格物&#xff0c;不格物就不能知道事物的具体描绘形式。流形大多数情况下是一种数学计算方式&#xff0c;可以将一个复杂的模型抽象…

Matplotlib Pyplot

Pyplot 是 Matplotlib 的子库&#xff0c;提供了和 MATLAB 类似的绘图 API。 Pyplot 是常用的绘图模块&#xff0c;能很方便让用户绘制 2D 图表。 Pyplot 包含一系列绘图函数的相关函数&#xff0c;每个函数会对当前的图像进行一些修改&#xff0c;例如&#xff1a;给图像加上…

ChatGPT | 申请与使用new bing的实用教程

1. 教程参考&#xff1a; https://juejin.cn/post/7199557716998078522 2.在参考上述教程遇到的问题与解决 2.1 下载dev浏览器的网址打不开 egde dev下载地址&#xff08;上面网站上的&#xff09;我电脑打不开 换用下面的网址即可 https://www.microsoftedgeinsider.com/z…

给定一个正整数字符串,使用Python正则表达式在其千分位上添加逗号

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 楼阁玲珑五云起&#xff0c;其中绰约多仙子。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python黄金青铜群【沐】问了一个Python正则表达式的问题…

MySQL开发工具评测,包含了Navicat、DBeaver、SQL Studio等12种

面对五花八门的MySQL客户端,开发者该如何选择,今天我整理了12种MySQL开发工具,从产品体验,功能完整度,云适配,计费模式,OS先容性等多个角度进行评估与分析,大家可根据自己的实际情况选择![在这里插入图片描述](https://img-blog.csdnimg.cn/56bdfc89afe743b9b87477d7c0521023.p…

SAP KANBAN 从入门到放弃系列之调拨模式

之前已经有三篇文章写了后台配置相关的介绍&#xff0c;这里不赘述。详见&#xff1a; PP-KANBAN-看板概述 SAP KANBAN 从入门到放弃系列之生产补货模式 SAP KANBAN 从入门到放弃系列之采购补货模式 第一步&#xff1a;补货策略-转库。不同的补充策略的控制类型有不同的作用…

【vue2 pc端】下拉滑动加载更多 vue-data-loading

官网地址 页面项目中使用 <template><!-- 空数据时显示 --><div class"nonono"><img src"/assets/img/404_cloud.png" alt"" v-if"goodslist.length < 0" class"nonnonoimg"></div>&…

燃气管道定位83KHZ地下电子标识器探测仪ED-8000操作指南

1、电子标识器探测工作 燃气管道定位83KHZ地下电子标识器探测仪ED-8000&#xff0c;探测时周边 3 米范围内不能有其他探测仪&#xff0c;保持探测仪垂直向 下&#xff0c;探测仪的末端距离地面 5~10cm 左右&#xff0c;延估计的埋地管线走向水平移动探测仪。当发现持续信号且信…

反射-Class类分析

反射相关的主要类 java.lang.Class&#xff1a;代表一个类&#xff0c;Class对象表示某个类加载后在堆中的对象java.lang.reflect.Method&#xff1a;代表类的方法&#xff0c;Method对象表示某个类的方法java.lang.reflect.Field&#xff1a;代表类的成员变量&#xff0c;Fie…

有手就行——基础XGBoost实战以 iris 数据集为例

基础 XGBoost 实战以 iris 数据集为例 1、导入数据2、数据预处理3、分训练集和测试集4、训练模型构建5、测试集预测准确度6、构建混淆矩阵7、特征重要性 对于很多只是小小使用机器学习&#xff0c;而不是深入了解的人来说&#xff0c;了解各种原理可能是十分痛苦的&#xff0c;…