cuda编程学习——第二个cuda程序(官方案例分析)!干货向(二)

news2024/11/18 16:23:22

前言:

最近在做三维重建,尤其是Nerf方面多视角合成工作的时候,意识到了cuda的编程计算可以大大提高其中渲染的计算,最明显的例子是Instant-ngp,Plenoxels等文章,因此后面会学Cuda一段时间,同时也就开了这个新坑。
因为笔者也是cuda新手,所以大家有问题的话可以评论区指出,一起学习进步!

Nvidia Cuda官方入门资料

运行环境:

Windows10,Visual Studio2019,显卡3050Ti
(大家自行根据自己笔记本情况去配环境,或者没有gpu,租借云服务器去学习也是可以的)

1:C++模板

首先从下面的C++代码开始
其作用为把2个数组元素相加,代码里设为1<<20,也就是在二进制下把00001左移20位,最终值为2^20,为1048576,大约位1million(百万),相加的值最终减去3.0,判断有没有误差。

#include <iostream>
#include <math.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<<20; // 1M 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;
  }

  // Run kernel on 1M elements on the CPU
  add(N, x, y);

  // 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));
    //fabs() 求浮点数的绝对值 fmax()返回2个参数最大的1个
  std::cout << "Max error: " << maxError << std::endl;

  // Free memory
  

输出如下
在这里插入图片描述

2:编写核函数cuda

(1)__global__可以理解为一个关键词,其告诉编译器该函数可以在GPU上运行,并且可以从CPU代码上调用
(2)这些__global__函数被称为内核,在GPU上运行的代码通常被称为device code,而在CPU上运行的代码被称为host code

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

3:Cuda下的内存分配(Memory Allocation)

(1)为了在GPU上进行计算,我需要分配GPU可访问的内存。CUDA中的统一内存通过提供系统中所有gpu和cpu可访问的单一内存空间使这一点变得容易。
(2)要在统一内存中分配数据,调用cudaMallocManaged(),它返回一个指针,您可以从host(CPU)code或device(GPU)code访问。要释放数据,只需将指针传递给cudaFree()。
(3)我只需要用对cudaMallocManaged()的调用替换上面代码中的new调用,并用对cudaFree()来执行释放数值操作

  // Allocate Unified Memory -- accessible from CPU or GPU
  float *x, *y;
  cudaMallocManaged(&x, N*sizeof(float));
  cudaMallocManaged(&y, N*sizeof(float));
//理解为,分配空间指向 指针x指向的空间地址,大小为 N* x  x表示float的空间大小,因为有N个浮点数据,大约为1 million

  ...

  // Free memory
  cudaFree(x);
  cudaFree(y);

(4)在GPU上调用核函数需要在参数列表前添加符号<<< >>>
简单理解,启动1个GPU线程来运行add()函数

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

(5)目前为止,完整代码如下

#include <iostream>
#include <math.h>
// Kernel function to add the elements of two arrays
__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<<20;
  float *x, *y;

  // Allocate Unified Memory – accessible from CPU or GPU
  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
  add<<<1, 1>>>(N, x, y);

  // Wait for GPU to finish before accessing on host
  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;
}

这只是第一步,因为正如所写的那样,这个内核只适用于单个线程,因为运行它的每个线程都会对整个数组执行add操作。此外,由于多个并行线程会读取和写入相同的位置,因此存在竞争条件。

4:并行线程计算

(1)找出内核运行时间的最简单方法是使用nvprof来运行它,nvprof是CUDA工具包附带的命令行GPU分析器。
(2)上述代码我们体验了利用GPU上一个线程来进行计算。那如何并行呢!
下面代码表示CUDA运行时在GPU上启动要使用多少并行线程,这里选取256个线程

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

(3)如果我在运行代码时只做了这样的修改,它将对每个线程执行一次计算,而不是将计算分散到并行线程上。为了正确地做到这一点,我需要修改内核,引入了两个量:
threadIdx.x:包含了the index of the thread within the block,块中的线程索引。此例中,index范围为0~255。
blockDim.x:包含了the size of thread block(number of threads in the thread block)线程块的大小,等于线程块中的线程数量。此例中,该值为256
在这里插入图片描述

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

下面是运行结果(这里其实计算给出结果相较之前会更快)
在这里插入图片描述
官网给出的比较:
这是一个很大的加速(从463ms减少到2.7ms)。
在这里插入图片描述

5:Out of the Blocks

(1)CUDA gpu有许多并行处理器,它们被分组为流多处理器(SMs)。每个SM可以运行多个并发线程块。为了充分利用CUDA GPU,kernel应启动多个thread blocks。

(2)到目前为止,我们可以想到执行配置的第一个参数指定了线程块的数量。这些并行线程块一起构成了所谓的网格grid。因为我有N个元素要处理,每个块有256个线程,所以我只需要计算块的数量来获得至少N个线程。我只是用N除以块大小(注意,如果N不是blockSize的倍数,就要四舍五入)。

int blockSize = 256;//并行线程数量
//使用N 除以块大小,表示 线程块数量
int numBlocks = (N + blockSize - 1) / blockSize;
add<<<numBlocks, blockSize>>>(N, x, y);

(3)我还需要更新内核代码,以考虑到线程块的整个网格。CUDA提供gridDim.x,其中包含网格中的块数量,以及blockIdx.x ,它包含网格中当前线程块的索引。
再引入两个量:
blockIdx.x:包含the index of the block with in the grid。网格中线程块的索引
gridDim.x:包含the size of the grid。网格大小,可以理解为网格中块的数量

图1说明了在CUDA中使用blockDim.x , gridDim.x 和 threadIdx.x对数组(一维)进行索引的方法。其思想是,每个线程通过计算其块开始的偏移量(块索引乘以块大小:blockIdx)来获得其索引。
这个想法是,每个线程通过计算其块开始的偏移量(块索引乘以块大小)来获得其索引:blockIdx.x* blockDim.x)。并在块中添加线程的索引threadIdx.x。
在这里插入图片描述

__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];
}

更新后的kernel还将stride设置为网格中(blockDim.x*gridDim.x)的线程总数。在CUDA内核中,这种类型的循环通常被称为网格跨步循环。

最终代码如下

#include <iostream>
#include <math.h>
#include<stdint.h>
#include<cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
// Kernel function to add the elements of two arrays
__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];
}

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

    // Allocate Unified Memory – accessible from CPU or GPU
    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
  //  add <<<1, 256 >>> (N, x, y);


    int blockSize = 256;//并行线程数量
//使用N 除以块大小,表示 线程块数量
    int numBlocks = (N + blockSize - 1) / blockSize;
    add << <numBlocks, blockSize >> > (N, x, y);



    // Wait for GPU to finish before accessing on host
    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;
}

6:结果比较(性能)

在这里插入图片描述

额外知识补充

代码被分成两部分,一部分是在CPU上,也称之为在Host上,另一部分是在GPU上,也称之为在device上。他们两者的关系如下图所示
在这里插入图片描述

网格grids和线程块blocks

网格grids,在上层,至多可以分成三维的blocks,在不同block当中的线程是不能通信的;线程块blocks在相对较低的层级,同样可以将线程分成三维,而在同一个块中的线程是可以通信的。

对于一个核函数,只能有一个grid,但是可以有多个block,之所以将线程划分为grid和block是为了使得结构更清晰,便于线程管理,灵活运用。

在这里插入图片描述

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

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

相关文章

Python代码最好的加密.pyd——easycython(Windows系统)

1 安装easycython 1.1 建议选用python 3.6及其以下的版本&#xff01;&#xff01; 1.2 CMD命令行 pip install easycython2 安装Visual Studio 2.1 下载 点击链接 https://visualstudio.microsoft.com/zh-hans/free-developer-offers/ 2.2 安装注意事项 记得勾选红色下图的…

渗透测试--3.1.社会工程学攻击

目录 社会工程学攻击 SET介绍 一、建立克隆钓鱼网站收集目标凭证 二、set工具集之木马欺骗实战反弹链接 三、后渗透阶段 1.查看主机系统信息 2.到处用户密码的hash值 3.获得shell控制台 日志清除 四、钓鱼邮件 1、测试邮箱的连通性 2、参数说明 3、Kali 内置了s…

位运算实现加减乘除(自用水文)

目录 位运算实现加法 位运算实现减法 位运算实现乘法 位运算实现除法 代码示例 PS&#xff1a;用位运算实现的加减乘除&#xff0c;其数据都是整型的(int、char、size_t等&#xff09; 位运算实现加法 LeetCode_2.两数相加_小白麋鹿的博客-CSDN博客https://yt030917.blo…

【Jmeter第一章】Jmeter实操详细教程(快速入门)

文章目录 1、前言2、Jmeter介绍3、Jmeter下载安装4、Jmeter快速入门4.1、切换为中文显示4.2、基本使用 总结 1、前言 本篇内容为Jmeter的简单使用介绍&#xff0c;是基础的使用技巧&#xff0c;希望能帮到各位&#xff0c;不足之处还望多多包涵&#xff0c;最后感谢您的阅览。…

ChatGPT工作提效之初探路径独孤九剑遇强则强

ChatGPT工作提效之遇强则强 前言一、如何使用ChatGPT二、ChatGPT实战应用三、ChatGPT会叫的小孩有奶吃工具类的交互问答类的交互开发类的交互 前言 读《笑傲江湖》西湖比剑时&#xff0c;对于独孤九剑1的解读印象颇为深刻。令狐冲被任我行这个高手激发出许多精妙的剑招。这独孤…

【原创】企业级别的Kafka配置--按照市场分区

企业级别的Kafka配置--按照市场分区 背景--Kafka广播按照市场分区生产者和消费者设计方案Kafka Broker设计消费消息时增加过滤条件消费者端利用多线程/多协程机制提高吞吐量 背景–Kafka广播 对于同一个Topic来说&#xff0c;每个消费者组都可以拿到这个Topic中的全部数据。消…

论文阅读《Gradient-based Camera Exposure Control for Outdoor Mobile Platforms》

摘要 本文介绍了一种用于移动机器人平台上图像处理和计算机视觉应用的自动调节相机曝光的新方法。由于大多数图像处理算法严重依赖于主要基于局部梯度信息的低级图像特征&#xff0c;因此我们认为梯度量可以确定适当的曝光水平&#xff0c;从而使相机能够以对照明条件具有鲁棒…

LaTeX详细安装教程|LaTeX 基础知识|LaTeX 常用语法|LaTeX 快速入门

latex安装教程 一、LaTeX 基础知识1.1 LaTeX 的特点1.2 LaTeX 的基本组成部分 二、TeXLive安装包下载三、安装步骤四、TeXstudio安装及简单使用五、快速入门&#xff08;LaTeX 常用语法&#xff09;5.1 文本格式5.2 数学公式5.3 LaTeX 支持有序列表和无序列表5.4 图片和表格 La…

vim自动文件头

注意&#xff1a;以下方法是安装了ycm后的方法&#xff0c;没安装是否好使不知道&#xff0c;建议还是安装ycm&#xff0c;原版的vim真不好用。 在用vim编辑代码的时候自动添加文件头还是比较有用的。 比如像下面这样&#xff0c;只要输入vim test.py文件头就自动添加上了。 …

【Spring框架】--03.AOP

文章目录 5.面向切面&#xff1a;AOP5.1场景模拟5.1.1声明接口5.1.2创建实现类5.1.3创建带日志功能的实现类5.1.4提出问题 5.2代理模式5.2.1概念5.2.2静态代理5.2.3动态代理5.2.4测试 5.3AOP概念及相关术语5.3.1概述5.3.2相关术语①横切关注点②通知&#xff08;增强&#xff0…

微服务架构 云原生应用从这一步开始

什么是云原生应用和微服务架构 云原生应用是一种设计和构建方式&#xff0c;旨在充分利用云计算的弹性、可扩展性和高可用性特性。云原生应用将应用程序的开发、交付和运行环境与云平台密切结合&#xff0c;以实现高度灵活、可靠和可扩展的部署。 云原生应用的核心原则包括以…

Java | 一分钟掌握定时任务 | 6 - Quartz定时任务

作者&#xff1a;Mars酱 声明&#xff1a;本文章由Mars酱原创&#xff0c;部分内容来源于网络&#xff0c;如有疑问请联系本人。 转载&#xff1a;欢迎转载&#xff0c;转载前先请联系我&#xff01; 前言 前几篇介绍了单体架构的定时任务解决方式&#xff0c;但是现代软件架构…

Mysql【基础篇】—— mysql安装和环境配置

Mysql【基础篇】—— mysql安装和环境配置&#x1f60e; Mysql 的概述Mysql下载安装和环境配置下载流程&#xff1a;Mysql启动&#xff1a;客户端连接方式一&#xff1a;使用MySQL提供的客户端命令行工具方式二&#xff1a;使用系统自带的命令行工具执行指令 总结撒花&#x1f…

tb-gateway配置OPC UA

1、安装模拟软件KEPServerEX 6 省略 2、配置OPC UA 安装好KEPServerEX 6之后,默认再电脑的最小化窗口会显示一个图标 右键点击图标,会显示一个OPC UA配置,然后点击配置,进入下面页面 点击添加按钮,弹出下面的弹窗 然后进行选择和配置,见下图,然后保存即可。 3、启动K…

【Linux】Linux编译器--vim的使用

&#x1f601;作者&#xff1a;日出等日落 &#x1f514;专栏&#xff1a;Linux 当你还不能对自己说今天学到了什么东西时&#xff0c;你就不要去睡觉。 ——利希顿堡 目录 vim是什么 vim安装 vim的基本概念 vim的基本操作 vim正常模式命令集 vim末行模…

R.I.P

0x01 这几天&#xff0c;陈皓老师&#xff08;网名&#xff1a;左耳朵耗子&#xff09;因心梗离世的消息相信大家也都看到了。 于我而言&#xff0c;震惊、难过之余&#xff0c;心里也是阵阵惋惜。 相信不少同学了解陈皓老师都是从他的个人博客酷壳CoolShell开始的。 同样&…

JavaWeb13-JavaScript 开发利器之 jQuery-02

1. jQuery 的 DOM 操作 1.1 查找节点, 修改属性 查找属性节点: 查找到所需要的元素之后, 可以调用 jQuery 对象的 attr() 方法来获取它的各种属性值 查找节点-应用实例 element-attribute.html <!DOCTYPE html> <html lang"en"> <head><met…

全网详细Django框架快速体验

一、安装Django (1)安装django命令 pip install django 二、命令行创建项目 执行命令创建项目 django-admin startproject 项目名称 如&#xff1a; django-admin startproject mysite 三、项目目录结构 mysite|----manage.py # 项目的管…

Nat. Mach. Intell 2023 | RT:首个统一分子性质预测(回归) 与条件生成的模型

原文标题&#xff1a;Regression Transformer enables concurrent sequence regression and generation for molecular language modelling 论文地址&#xff1a;Regression Transformer enables concurrent sequence regression and generation for molecular language model…

Servlet编程---Day 07

目录 一、过滤器概述 二、过滤器使用 &#xff08;一&#xff09;开发第一个过滤器 &#xff08;二&#xff09;过滤器的生命周期 &#xff08;三&#xff09;FilterChain(过滤器链) 1.过滤器链认识 2.过滤器链代码实现 3.过滤器链顺序 &#xff08;四&#xff09;请求…