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

目录

    • 前言
    • 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上开辟空间并且放数据进去,将数据复制到GPUfloat* 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.hqwc.cn/news/13113.html

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

相关文章

机器学习之多元微积分

机器学习的多元微积分跟高等数学中的多元微积分有很多不同之处。 机器学习中的变量都是向量或者矩阵机器学习中的函数一般都是线性函数&#xff0c;而不是高数中的曲线和曲面、体积等函数。因此&#xff0c;机器学习中的微积分跟线性代数结合在一起。机器学习中导数的分子分母…

山西电力市场日前价格预测【2023-07-09】

日前价格预测 预测明日&#xff08;2023-07-09&#xff09;山西电力市场全天平均日前电价为386.09元/MWh。其中&#xff0c;最高日前价格为505.65元/MWh&#xff0c;预计出现在21: 30。最低日前电价为286.38元/MWh&#xff0c;预计出现在13: 30。 以上预测仅供学习参考&#x…

新版本FasterTransformer的FUSED_MHA

关于 UNFUSED_PADDED_MHA VS FUSED_MHA FUSED_MHA用了另一种kernel的执行方法(和添加链接描述相同,将在下一个section说明)UNFUSED_PADDED 的 KERNELS执行代码在 src/fastertransformer/kernels/unfused_attention_kernels.cu enum class AttentionType {UNFUSED_MHA,UNFUSED…

OpenCloudOS社区开源,助力软件开发

早前红帽宣布限制源代码访问性的政策&#xff0c;并解释说RHEL相关源码仅通过CentOS Stream公开&#xff0c;付费客户和合作伙伴可通过Red Hat Customer Portal访问到源代码&#xff0c;由此也导致非客户获取源码越来越麻烦&#xff0c; 据了解&#xff0c;CentOS是红帽发行的…

CentOS 7镜像下载 以及 DVD ISO 和 Minimal ISO 等各版本的区别介绍

1.官网下载 官网下载地址&#xff1a;官网下载链接 点击进入下载页面&#xff0c;随便选择一个下载即可&#xff08;不推荐&#xff0c;推荐阿里云下载&#xff0c;见下文&#xff09; 阿里云下载站点&#xff08;速度非常快推荐&#xff09; 阿里云下载链接&#xff1a; http…

第十二章 kafka

Producer:Producer即生产者,消息的产生者,是消息的入口。 kafka cluster: Broker:Broker是kafka实例,每个服务器上有一个或多个kafka的实例,我们姑且认为每个broker对应一台服务器。每个kafka集群内的broker都有一个不重复的编号,如图中的broker-0、broker-1等…… 主…

Spring 最全入门教程详解

目录 一、Spring Framwork简介1. Spring Framework五大功能模块2. Spring Framework特性 二、IOC容器1. IOC思想2. IOC容器在Spring中的实现3.基于xml管理Bean3.1 引入依赖3.2 创建类3.3 创建Spring的配置文件3.4 创建测试类3.5 总结 4.DI依赖注入4.1 setter注入4.2 构造器注入…

Mycat【Mycat高可用(安装配置HAProxy、安装配置Keepalived)】(八)-全面详解(学习总结---从入门到深化)

目录 Mycat高可用_安装配置HAProxy Mycat高可用_安装配置Keepalived 复习&#xff1a; Mycat高可用_安装配置HAProxy 安装配置HAProxy 查看列表 yum list | grep haproxy yum安装 yum -y install haproxy 修改配置文件 $ vim /etc/haproxy/haproxy.cfg 启动HAProxy …

学习Angular的编程之旅

目录 1、简介 2、特点 2.1 横跨多种平台 2.2 速度与性能 2.3 美妙的工具 3、Angular 应用&#xff1a;知识要点 3.1 组件 3.2 模板 3.3 依赖注入 4、与其他框架的对比 1、简介 Angular 是一个应用设计框架与开发平台&#xff0c;旨在创建高效而精致的单页面应用。 A…

jvm对象创建和内存分配优化

一、创建对象过程 1、类加载检测 虚拟机遇到一条new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否是否已被加载、解析和初始化过。如果没有&#xff0c;那必须先执行相应的类加载过程。 …

Flask学习笔记(2)应用部署

本文将介绍如何部署Flask应用。   部署Flask应用&#xff0c;主要是要运用多线程与多进程&#xff0c;提高接口的并发能力。我们以下面的Python代码&#xff08;server.py&#xff09;为例进行演示&#xff1a; # -*- coding: utf-8 -*- import time import datetime from f…

密码学学习笔记(五):Hash Functions - 哈希函数1

简介 什么是密码学中的哈希函数&#xff1f; 哈希函数是一种快速算法&#xff0c;它接受任何长度的输入&#xff0c;并产生一个固定长度的随机输出&#xff0c;称为摘要(digest)&#xff0c;比如&#xff1a; MD4, MD5: 128-bit output (broken) •SHA-1: 160-bit output (b…