3.4.流的学习,异步任务的管理

news2024/11/13 8:51:01

目录

    • 前言
    • 1. 流
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习精简 CUDA 教程-流的学习,异步任务的管理

课程大纲可看下面的思维导图

在这里插入图片描述

1. 流

关于流的知识你需要了解:

  1. 流是一种基于 context 之上的任务管道(任务队列)抽象,一个 context 可以创建 n 个流
  2. 流是异步控制的主要方式(CUDA 上高性能并发通过流来实现)
  3. nullptr 表示默认流,每个线程都是自己的默认流

我们先举个简单的例子来理解串行同步模式,假如你的女朋友想吃东西了,让你去买,那么整个时序图如下所示:

在这里插入图片描述

图1-1 串行同步模式时序图

在串行同步模式下女朋友从想吃苹果到吃到苹果这段时间内什么也不能做,需要等待男朋友把苹果买回来了的信息。

你也可以从函数的角度来思考,将发信息买苹果看作一个函数调用,这个函数可能有出门,去买苹果等方法,买回来了之后返回结果,而函数拿到这个结果再做下一步处理,从函数调用到拿到返回值结果这段时间内其实什么也没做,这个就是一个典型的串行同步方式。

我们再来看下异步的方式

在这里插入图片描述

图1-2 异步模式时序图

在异步模式下,女朋友发完想吃苹果的消息后就去写作业了,不会傻乎乎的等着,这也符合我们生活的基本行为。可能写了会作业又想吃西瓜了,然后又给男朋友发消息说想吃西瓜,男朋友又屁颠屁颠的去买西瓜,突然可能又想喝奶茶了,然后发消息跟男朋友说想喝奶茶,发完消息后是不是又可以干别的事情,比如打游戏等等。

最后等待男朋友回来即可,当然你可以在任意时候等待拿到你想要的东西,简单来说你可以选择在刚打完游戏的时候就去等(可能你比较渴😂),你也可以把你的事情忙完后再去等,甚至你可以等到你男朋友买回来一段时间后再去拿东西,这个从什么时间开始去等是你能控制的,也就是说你能决定什么时候去等待拿回你想要得到的结果

从上面的例子中,我们来对比学习下流

  1. 上面的例子中,男朋友的微信消息,就是任务队列,流的一种抽象
  2. 女朋友发出指令后,她可以做任何事情,无需等待指令执行完毕(指令发出的耗时也是极短的)
  3. 也就是说,异步操作,执行的代码加入流的队列后,立即返回,不耽误时间
  4. 女朋友发的指令被送到流中排队,男朋友根据流的队列,顺序执行
  5. 女朋友可以选择性在需要的时候等待所有的执行结果
  6. 新建一个流,就是新建一个男朋友,给他发指令就是给他发微信,你可以新建很多个男朋友
  7. 通过 cudaEvent 可以选择性等待任务队列中的部分任务是否就绪
  • 比如女朋友在发送买西瓜消息的同时添加了 cudaEvent,也就是等同于告诉男朋友如果你买好了记得给我一个回应,让我知道你已经完成了这个事情

下面我们来看下流案例的示例代码:


// CUDA运行时头文件
#include <cuda_runtime.h>

#include <stdio.h>
#include <string.h>

#define checkRuntime(op)  __check_cuda_runtime((op), #op, __FILE__, __LINE__)

bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
    if(code != cudaSuccess){    
        const char* err_name = cudaGetErrorName(code);    
        const char* err_message = cudaGetErrorString(code);  
        printf("runtime error %s:%d  %s failed. \n  code = %s, message = %s\n", file, line, op, err_name, err_message);   
        return false;
    }
    return true;
}

int main(){

    int device_id = 0;
    checkRuntime(cudaSetDevice(device_id));

    cudaStream_t stream = nullptr;
    checkRuntime(cudaStreamCreate(&stream));

    // 在GPU上开辟空间
    float* memory_device = nullptr;
    checkRuntime(cudaMalloc(&memory_device, 100 * sizeof(float)));

    // 在CPU上开辟空间并且放数据进去,将数据复制到GPU
    float* memory_host = new float[100];
    memory_host[2] = 520.25;
    checkRuntime(cudaMemcpyAsync(memory_device, memory_host, sizeof(float) * 100, cudaMemcpyHostToDevice, stream)); // 异步复制操作,主线程不需要等待复制结束才继续

    // 在CPU上开辟pin memory,并将GPU上的数据复制回来 
    float* memory_page_locked = nullptr;
    checkRuntime(cudaMallocHost(&memory_page_locked, 100 * sizeof(float)));
    checkRuntime(cudaMemcpyAsync(memory_page_locked, memory_device, sizeof(float) * 100, cudaMemcpyDeviceToHost, stream)); // 异步复制操作,主线程不需要等待复制结束才继续
    checkRuntime(cudaStreamSynchronize(stream));
    
    printf("%f\n", memory_page_locked[2]);
    
    // 释放内存
    checkRuntime(cudaFreeHost(memory_page_locked));
    checkRuntime(cudaFree(memory_device));
    checkRuntime(cudaStreamDestroy(stream));
    delete [] memory_host;
    return 0;
}

运行效果如下:

在这里插入图片描述

图1-3 流案例运行结果

上述代码展示了使用 CUDA 中的流(stream)来进行异步数据传输和内存管理,首先我们通过 cudaStreamCreate 创建了一个流对象,用于管理异步操作。然后使用 cudaMemcpyAsync 将 CPU 上的数据异步复制到 GPU 上,注意 cudaMemcpyAsync 相比于 cudaMemcpy 多了一个参数,也就是流。

在异步复制的时候,发出指令立即返回,并不等待复制完成,因此你可以看到如果你在后面添加打印语句,可以发现数据并没有复制,主线程并不需要等待复制完成,可以继续执行后续操作。

接着同样又利用异步复制将 GPU 上的数据复制到 CPU 上来,最后使用 cudaStreamSynchronize 同步流,确保前面的异步复制操作全部完成(也就是男朋友将东西全买回来了😃),然后打印获取的结果,那你可以发现真正的耗时其实是发生在这一步的。

通过使用流,可以将数据传输和内存操作于主线程的计算任务异步进行,从而提高了并行性和性能。

对于流的使用,你需要注意的是:

  1. 指令发出后,流队列中储存的是指令参数(也就是指针或者形参),不能加入队列后立即释放参数指针,这会导致流队列执行该指令时指针失效而错误
  2. 应当在十分肯定流已经不需要这个指针后,才进行修改或者释放,否则会有非预期结果出现
  • 比如说当你在执行 cudaMemcpyAsync 后立马执行 delete [] memory_host 将 CPU 上数据释放,那其实复制这个过程是没有完成的,而你又将数据进行释放了,因此会产生一些预期外的结果,这点值得大家注意。因此,你需要确保流已经不需要这个指针后,才对其进行操作
  • 举个更简单的例子:比如你给钱让男朋友买西瓜,他没有钱,他刚到店拿好西瓜,你把转的钱撤回去了。那么此时你无法预知他是否会跟店家闹起来矛盾,还是屁颠的回去。如果想得到预期结果,必须得让买完西瓜结束后再处理钱的事情

关于流的知识点需要知道的是:(from chatGPT)

  1. stream 是一个流句柄,可以当做是一个队列
  • cuda 执行器从 stream 中一条条的读取并执行指令
  • 例如 cudaMemcpyAsyn 函数等同于向 stream 这个队列中加入一个 cudaMemcpy 指令并排队
  • 使用到了 stream 的函数,便立即向 stream 中加入指令后立即返回,并不会等待指令执行结束
  • 通过 cudaStreamSynchronize 函数,等待 stream 中所有指令执行完毕,也就是队列为空
  1. 当使用 stream 时,要注意
  • 由于异步函数会立即返回,因此传递进入的参数要考虑其生命周期,应确认函数调用结束后再做释放
  1. 还可以向 stream 中加入 Event,用以监控是否到达了某个检查点
  • cudaEventCreate,创建事件
  • cudaEventRecord,记录事件,即在 stream 中加入某个事件,当队列执行到该事件后,修改其状态
  • cudaEventQuery,查询事件当前状态
  • cudaEventElapsedTime,计算两个事件之前经历的时间间隔,若要统计某些核函数执行时间,请使用这个函数,能够得到最准确的统计
  • cudaEventSynchronize,同步某个事件,等待事件到达
  • cudaStreamWaitEvent,等待流的某个事件
  1. 默认流,对于 cudaMemcpy 等同步函数,其等价于执行了
  • cudaMemcpyAsync(… 默认流) 加入队列
  • cudaStreamSynchronize(默认流) 等待执行完成
  • 默认流与当前设备上下文类似,是与当前设备进行的关联
  • 因此,如果大量使用默认流,会导致性能低下

总结

本次课程学习了流,流是 CUDA 编程中异步控制的主要方式。我们通过女朋友让男朋友买东西这个非常生动形象的例子来说明了流的使用,男朋友的微信消息其实就是流的任务队列,而女朋友在发出任务指令后就可以做一些其它的事情,因此可以大大提高效率。

值得注意的是,女朋友可以在需要的时候等待执行的结果,她可以自主决定什么时间点去等待,同时还可以创建多个流来实现并发。

对应到代码层面上,主要通过 cudaMemcpyAsync 这个异步复制函数来讲解流的使用,该函数多了一个流的参数,用来做异步,我们最后通过 cudaStreamSynchronize 函数来统一等待流队列中所有的操作结束,主要的耗时其实也就是在这一步。我们还需要注意的一个点就是流队列中储存的指令参数,你必须确定已经不需要后才能进行修改或者释放,否则会有一些意外的结果。

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

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

相关文章

MySQL库表操作作业

创建数据库 mysql> create database Market; mysql> use Market; 创建表和约束 mysql> create table customers(c_num int(11) primary key not null UNIQUE Key auto_increment , -> c_name varchar(50), -> c_city varchar(50), -> c_birth datetime…

九、HTML中的定位

1、定位 position static 默认值 没有使用定位 relactive 相对定位 absolute 绝对定位 fixed 锚定 标准文档流 标准文档流 从上到下&#xff0c;从左向右&#xff0c;依次显示网页中的每一个元素 元素分类 行内元素 依次一个挨着一个显示 块级元素 独占一行 static 定位 以…

【STM32智能车】智能寻迹

【STM32智能车】智能寻迹 基础算法寻迹小车 我们之前说了到了寻迹这里会涉及到一些算法&#xff0c;不过各位小伙伴可以放心&#xff0c;我们这里用的是一些基础算法。不需要公式&#xff0c;只需要进行简单的判断就行。 基础算法 寻迹车的程序算法如下&#xff1a; 初始化&…

MySQL数据库小练习1

1.创建数据库&#xff0c;删除数据库&#xff0c;查询创建数据的语句&#xff0c;使用数据库&#xff0c;查询当前默认的数据库以及使用的编码方式校验规则 创建数据库及使用数据库&#xff1a; create database hzc default character set utf8mb4 collate utf8mb4_0900_ai_…

uniapp电子签名以及竖屏签名后内容旋转90度变为横屏图片

用该插件挺不错的 电子签名插件地址 如果你一个页面要用多个该插件&#xff0c;就改成不同的cavas-id&#xff0c;修改插件源码 效果图 竖屏写 旋转成横屏图片 插件内 在拿到签名临时地址后的页面 <!-- 旋转图片canvas --> <canvas canvas-id"camCacnvs&quo…

第二次CCF计算机软件能力认证

第一题&#xff1a;相邻数对 给定 n 个不同的整数&#xff0c;问这些数中有多少对整数&#xff0c;它们的值正好相差 1。 输出格式 输入的第一行包含一个整数 n&#xff0c;表示给定整数的个数。 第二行包含所给定的 n 个整数。 输出格式 输出一个整数&#xff0c;表示值正好相…

华为OD计算工时python脚本

前言 刚入职不知道工时要平均每天满8小时&#xff0c;并且看不到每天的实际工时&#xff0c;一气之下花了一个中午写了个脚本计算每天的工时&#xff0c;分享一下&#xff0c;不同地区的兄弟需要修改一下午休和晚饭时间才能使用。 文件位置 把welink上 上下班时间输入work.c…

支持向量机推导之r||w||=1的限制转化

支持向量机推导之r||w||1的限制转化 很多同学肯定是学过支持向量机的&#xff0c;也可能大致的理解了支持向量机这个算法&#xff0c;我想大部分人在学习这个算法的时候&#xff0c;对于推导过程有一步应该是不太理解。 我先简要介绍一下SVM,SVM的核心思想在于找到一个多维空间…

getCurrentInstance

https://blog.csdn.net/m0_46318298/article/details/130726043 注&#xff1a;$是在vue中所有实例中都可用的一个简单约定&#xff0c;这样做会避免和已被定义的数据&#xff0c;方法&#xff0c;计算属性产生冲突。

Leetcode-每日一题【203.移除链表元素】

题目 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6输出&#xff1a;[1,2,3,4,5] 示例 2&#xff1a; 输入&#…

nginx白名单配置

在有得项目中&#xff0c;我们会希望端口只有特定的用户可以访问&#xff0c;这时候就需要配置nginx的白名单&#xff0c;接来下展示一下白名单的配置和应用 vi /etc/nginx/nginx.conf server {listen 80;listen [::]:80;server_name _;root /usr/share/…

springboot第29集:springboot项目详细

public static LoginUser getLoginUser()&#xff1a;该行声明了一个公共的静态方法 getLoginUser()&#xff0c;它的返回类型是 LoginUser。try&#xff1a;开始一个 try 块&#xff0c;用于处理接下来的代码中可能发生的异常。return (LoginUser) getAuthentication().getPri…

STL好难(6):queue队列的使用

目录 1.queue的介绍 2.queue的使用&#xff1a; 3.queue的模拟实现&#xff1a; 4.deque的介绍&#xff1a; 5.deque的函数接口和底层原理&#xff1a; 6.deque的优缺点&#xff1a; 1.queue的介绍 queue的文档内容 1. 队列是一种容器适配器&#xff0c;专门用于在FIFO上…

二次-InsCode Stable Diffusion 美图活动一期

模型&#xff1a; AbyssOrangeMix2 - SFW_Soft NSFW_AbyssOrangeMix2_sfw.safetensors 参数配置&#xff1a; 正&#xff1a;Mountains and seas, people 负&#xff1a;NSFW, (worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, ((monochr…

C# .NET 如何调用 SAP RFC 接口

1.分析传参结构 SAP 传参格式对应 .NET 参数格式 SAP 参数.NET 参数参数类型import(导入)——关联类型为数据元素Param单个变量参数import(导出)——关联类型为结构体Struct结构体tableTable表 下面是 SAP 对应参数类型&#xff1a; 2.web.config 配置 配置文件需要客户端…

Python学习笔记(十六)————异常相关

目录 &#xff08;1&#xff09;异常概念 &#xff08;2&#xff09;异常的捕获 ①异常捕获的原因 ②捕获常规异常 ③捕获指定异常 ④捕获多个异常 ⑤ 捕获异常并输出描述信息 ⑥捕获所有异常 ⑦异常else ⑧异常的finally &#xff08;3&#xff09;异常的传递 &#xff08…

自动化测试 selenium 篇

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 什么是自动化测试&#xff1f;Selenium 介绍Selenium 是什么Selenium 特点工作原理 seleniumJava环境搭建ChromeJava1.下载ch…

uni-app:删除默认title

去除前&#xff1a; 可以看到有两个title 去除后&#xff1a; 可以看出就只有手机顶部的title了 "navigationStyle": "custom",//删除默认title

堆的向上与向下调整

目录 一、堆 1、概念 2、性质 二、向上调整 三、向下调整 四、建堆的比较 1.向上调整建堆 2.向下调整建堆 3.比较 五、总结 一、堆 1、概念 如果有一个关键码的集合K {k0k1&#xff0c;k2&#xff0c;…kn-1}&#xff0c;把它的所有元素按完全二叉树的顺序存储方式存…

怎么学习PHP错误处理和调试? - 易智编译EaseEditing

学习PHP错误处理和调试技术可以通过以下步骤&#xff1a; 理解错误类型&#xff1a; 了解PHP中常见的错误类型&#xff0c;如语法错误、运行时错误和逻辑错误等。学习它们的特点和常见原因&#xff0c;以便更好地定位和解决问题。 错误报告设置&#xff1a; 在开发环境中&am…