CUDA编程之CUDA流

news2025/2/26 2:56:18

文章目录

  • 前言
  • CUDA流
    • 在默认流中重叠主机与设备
    • 用非默认CUDA流重叠多个核函数的执行
      • 重叠多个核函数的例子
    • 用非默认CUDA流重叠核函数的执行与数据传递
      • 不可分页主机内存与异步的数据传输函数
  • 总结
    • 参考


前言

    CUDA程序的并行层次主要有两个,一个是核函数内部的并行,一个是核函数外部的。合理使用CUDA流能实现核函数外部的并行。

CUDA流

核函数外部并行:
(1)核函数计算与数据传输之间的并行
(2)主机计算与数据传输之间的并行
(3)不同数据传输之间的并行
(4)核函数计算与主机计算之间的并行
(5)不同核函数之间的并行

    CUDA流:一个CUDA流指的是由主机发出的在一个设备中执行的CUDA操作序列。CUDA流各个操作的次序是由主机控制的,按照主机发布的次序执行。两个不同的CUDA流中的操作不一定按照顺序执行,和线程一样,要么并发要么交错地执行。
    任何CUDA操作都存在于某个CUDA流中,要么是默认流和空流,要么是指定地非空流。前面的CUDA程序没有指定的都是默认和空流中执行的。
非默认的CUDA流实在主机端产生与销毁的。

// 创建
cudaError_r cudaStreamCream_t(cudaStream_t*);  // 在yolov5_trt中使用过
// 销毁
cudaError_r cudaStreamDestroy(cudaStream_t); 

由创建的代码可知,输入参数是cudaStream_t类型指针,返回值类型是一个错误代码。

不同CUDA流之间的并发

    主机向某个CUDA流中发布一系列命令后必须马上获得程序的控制权,不用等待该CUDA流中的命令在设备中执行完毕。这样,就可以通过主机生成多个独立的CUDA流。
同时,CUDA运行时API还提供了两个函数:

// 这两个函数和等待和守护线程有点像
// 会强制阻塞主机,知道cuda流stream中的所有操作都执行完毕。
cudaError_r cudaStreamSynchronize(cudaStream_t stream);
// 不阻塞主机,只是检查CUDA流stream中的所有操作是否都执行完毕。
cudaError_r cudaStreamQuery(cudaStream_t stream);

在默认流中重叠主机与设备

合理利用主机与设备之间的执行次序。如前面的简单的数组求和的核函数:

   // 将某些数据从主机复制到设备上
    cudaMemcpy(d_x,h_x,M,cudaMemcpyHostToDevice);
    cudaMemcpy(d_y,h_y,M,cudaMemcpyHostToDevice);
    // 调用核函数在设备中进行计算,数组求和
    const int block_size = 128;  // 不同型号的GPU有线程限制,开普勒到图灵最大为1024
    const int gride_size = N/block_size;
    add<<<gride_size,block_size>>>(d_x,d_y,d_z);
    // 将某些数据从设备复制到主机上,这个数据传输函数隐式的起到了同步主机与设备的作用,所以后面用不用cudaDeviceSynchronize都可以
    cudaMemcpy(h_z,d_z,M,cudaMemcpyDeviceToHost);

    从主机看,数据传输是同步的。在主机执行核函数之前的CUDA操作语句将在默认的CUDA流中按代码的顺序执行。在进行数据传输时,主机是闲置的,不能进行其他操作。
    不同的是,在执行核函数时,核函数的启动是异步的。主机发出执行的命令,不会等核函数执行完毕,而会立刻的到程序的控制权,往下执行。执行到从设备到主机传输数据这条语句,该语句不会立即执行。因为这是默认流中的CUDA操作,必须等待前一个CUDA操作执行完毕才会开始执行。
    从上面的分析可知,核函数启动异步,对默认流中CUDA的操作会阻塞,但是对主机中的程序不会进行阻塞。如果调用核函数下一句是主机中的某个计算任务,那么主机就会在设备执行核函数的同时去进行一些计算。这样主机和设备就可以同时进行计算。
    当然,重叠主机与设备的计算要考虑两者计算的时间。一般来说设备函数的计算速度是主机函数的10倍左右。当设备函数执行完的时间与接下来的主机函数执行完的时间差不多时,两者擦汗不读时间结束,加速效果最好。当主机函数与设备函数的计算时间相差很多的情况下,设备函数占主要计算时间或者主机函数占主要时间,加速效果就差了。

用非默认CUDA流重叠多个核函数的执行

    一个默认流可以实现主机计算与设备计算的并行,但是多个核函数之间的并行必须使用多个CUDA流。

重叠多个核函数的例子

使用非默认流时,核函数的执行配置中必须包含一个流对象。例如,核函数有如下三种调用方式:

add<<<gride_size,block_size>>>();
add<<<gride_size,block_size,shared>>>();
add<<<N_gride,N_block_size,N_shared,stream_id>>>();

    其中,第一个调用方式在默认流中执行。第二个是使用了动态共享内存的,同样在默认流中执行。只有第三个调用方式,说明核函数在编号为stream_id的CUDA流中执行,而且可以使用动态共享内存,不使用动态共享内存的情况下,动态共享字节参数不能省略,必须为。
如修改原来的核函数中数组相加的程序:

// 多个非默认流
 for (int n = 0; n < num; ++n)
        {
            int offset = n * N1;
            add<<<grid_size, block_size, 0, streams[n]>>>
            (d_x + offset, d_y + offset, d_z + offset);
        }
-----------------------------------------------------------------------------
void __global__ add(const real *d_x, const real *d_y, real *d_z)
{
    const int n = blockDim.x * blockIdx.x + threadIdx.x;
    if (n < N1)
    {
    	// 使用了for循环进行数组元素相加,这是为了后面的计时方便,不使用也行。
        for (int i = 0; i < 1000000; ++i)
        {
            d_z[n] = d_x[n] + d_y[n];
        }
    }
}

和最初的代码,有两个区别:
(1)在调用核函数的时候,使用了for循环,表示使用非默认流中重叠多个核函数的执行。n,表示CUDA流的索引。offset 表示多个核函数中,每个核函数使用N1个线程。
(2)在每个核函数计算时,使用了for循环进行数组元素相加,这是为了后面的计时方便,不使用也行。
在测试中发现,使用多个流相对于使用一个流有了加速。但是,存在加速比的限制:
(1)当所有CUDA流中对应核函数的线程总数和超过某个值时,再增加流的数目就不会带来更高的加速比了。
(2)单个GPU中能够并发执行的核函数个数的上限,不同的GPU架构有不同的上限。

用非默认CUDA流重叠核函数的执行与数据传递

不可分页主机内存与异步的数据传输函数

    要实现核函数执行与数据传输的并发(重叠),必须让这两个操作处于不同的非默认流,而且数据传输必须使用cudaMemcpy()函数的异步版本,即cudaMemcpyAsync()函数(前面tensorrt部署中有使用)。异步传输由GPU中的DMA直接实现,不需要主机参与。如果用同步的数据传输,主机无法再一个流中进行数据传输时,去另一个流调用核函数。这样核函数执行与数据传输的并发也就无法实现。
在这里插入图片描述
    异步的数据传输函数,只比同步的多了一个参数,表示所在流的变量。在使用异步数据传输函数时,需要将主机内存定义为不可分页内存或者固定内存。不可分页主机内存分分配可以由以下两个函数中任何一个实现:
在这里插入图片描述
在这里插入图片描述

尝试使用不同的流执行不同的操作来提升性能。

    一般一个cuda程序要有主机向设备进行数据传输(H2D),核函数的调用(KER),设备向主机进行数据传输(D2H)。这三个操作在一个CUDA流中执行的顺序:
s t r e a m 0 : H 2 D − > K E R − > D 2 H \qquad \qquad \qquad \qquad stream0 : H2D -> KER -> D2H stream0:H2D>KER>D2H
如果简单的将3个操作放入3个不同的流中:
s t r e a m 0 : H 2 D \qquad \qquad \qquad \qquad stream0 : H2D stream0:H2D
s t r e a m 1 : − > K E R \qquad \qquad \qquad \qquad stream1 : \quad \qquad -> KER stream1:>KER
s t r e a m 2 : − > D 2 H \qquad \qquad \qquad \qquad stream2: \qquad \qquad \qquad \qquad -> D2H stream2:>D2H
    这样操作并不能带来性能提升。必须创造出在逻辑上可以并发执行的CUDA操作,可以将以上3个CUDA操作都分成若干等份,然后在每一个流中发布一个CUDA操作序列。
s t r e a m 0 : H 2 D − > K E R − > D 2 H \qquad \qquad \qquad \qquad stream0 : H2D -> KER -> D2H stream0:H2D>KER>D2H
s t r e a m 1 : H 2 D − > K E R − > D 2 H \qquad \qquad \qquad \qquad stream1 : \qquad \qquad H2D -> KER -> D2H stream1:H2D>KER>D2H
    如上面使用两个流的情况,每个CUDA操作所处理的数据量只有使用一个CUDA流时的一半。其中H2D这里不能并发的执行,是受硬件资源的限制。如果H2D ,KER ,D2H这三个操作执行的时间都相同,那么可以有效的隐藏两个CUDA操作,使得总的执行效率相比使用单个CUDA流的情况提升 6 / 4 = 1.5 6/4=1.5 6/4=1.5倍。如下给出使用非默认CUDA流重叠核函数的执行与数据传递的示例:

// 部分代码
void __global__ add(const real *x, const real *y, real *z, int N)
{
    const int n = blockDim.x * blockIdx.x + threadIdx.x;
    if (n < N)
    {
     	// 和上面一样故意让核函数求和操作重复40次,让核函数和数据传输所用时间相当。
        for (int i = 0; i < 40; ++i)
        {
            z[n] = x[n] + y[n];
        }
    }
}

void timing
(
    const real *h_x, const real *h_y, real *h_z,
    real *d_x, real *d_y, real *d_z,
    const int num
)
{
    int N1 = N / num;
    int M1 = M / num;

    float t_sum = 0;
    float t2_sum = 0;

    for (int repeat = 0; repeat <= NUM_REPEATS; ++repeat)
    {
        cudaEvent_t start, stop;
        CHECK(cudaEventCreate(&start));
        CHECK(cudaEventCreate(&stop));
        CHECK(cudaEventRecord(start));
        cudaEventQuery(start);
		// num个CUDA流
        for (int i = 0; i < num; i++)
        {
            int offset = i * N1;
            // 使用异步数据传输
            CHECK(cudaMemcpyAsync(d_x + offset, h_x + offset, M1, 
                cudaMemcpyHostToDevice, streams[i]));
            CHECK(cudaMemcpyAsync(d_y + offset, h_y + offset, M1, 
                cudaMemcpyHostToDevice, streams[i]));
            int block_size = 128;
            int grid_size = (N1 - 1) / block_size + 1;
            // 非默认CUDA流
            add<<<grid_size, block_size, 0, streams[i]>>>
            (d_x + offset, d_y + offset, d_z + offset, N1);

            CHECK(cudaMemcpyAsync(h_z + offset, d_z + offset, M1, 
                cudaMemcpyDeviceToHost, streams[i]));
        }

总结

CUDA流的基础知识总结

参考

如博客内容有侵权行为,可及时联系删除!
CUDA 编程:基础与实践
https://docs.nvidia.com/cuda/
https://docs.nvidia.com/cuda/cuda-runtime-api
https://github.com/brucefan1983/CUDA-Programming

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

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

相关文章

C++面向对象——C++ 重载运算符和重载函数

C面向对象——C 重载运算符和重载函数C 重载运算符和重载函数C 中的函数重载C 中的运算符重载运算符重载实例C 一元运算符重载C 二元运算符重载C 关系运算符重载C 和 -- 运算符重载C 赋值运算符重载C 函数调用运算符 () 重载C 下标运算符 [] 重载C 类成员访问运算符 -> 重载…

三、进程通信

一、基础知识数据传输一个进程将他的数据发送给另一个进程资源共享多个进程间共享同样的资源通知时间一个进程向另一个进程发送消息&#xff0c;通知他们发生了某种事情通信方式&#xff1a;管道和有名管道信号signal消息队列共享内存信号量套接字二、管道&#xff1a;无名管道…

c++11 标准模板(STL)(std::multiset)(六)

定义于头文件 <set> template< class Key, class Compare std::less<Key>, class Allocator std::allocator<Key> > class multiset;(1)namespace pmr { template <class Key, class Compare std::less<Key>> usi…

基于python Django 餐馆点菜管理系统

问题描述&#xff1a; 随着网络的迅速发展&#xff0c;越来越多的人开始接受甚至时依赖了网络营业的这种交易形式&#xff0c;传统的点菜模式不仅浪费时间&#xff0c;效率低下&#xff0c;而且特别耗费成本与人力&#xff0c;因此不少商家开始使用网上点菜系统。网上点菜系统是…

皮尔森相关系数(Pearson correlation coefficient)

最近在看脑机接口的网络&#xff0c;看到有使用通道的皮尔森相关系数作为特征的方法&#xff0c;这里记录一下皮尔森相关系数的学习内容&#xff0c;方便以后查阅。 皮尔森相关系数(Pearson correlation coefficient&#xff09;相关系数简单相关系数复相关系数典型相关系数参考…

【MySQL】MySQL中的数学函数有哪些?

数学函数MySQL函数简介数学函数1.绝对值函数ABS&#xff08;x&#xff09;和返回圆周率的函数PI&#xff08;&#xff09;2.平方根函数SQRT&#xff08;x&#xff09;和求余函数MOD&#xff08;x&#xff0c;y&#xff09;3.获取整数的函数CEIL&#xff08;x&#xff09;、CEIL…

关于Json Web Token(token)在前后端的实践思考

1、前言 啥也不说了&#xff0c;直接进入正题&#xff0c;来学习一下Token在前端和后端的简单应用分析 Token是在客户端频繁向服务端请求数据&#xff0c;服务端频繁的去数据库查询用户名和密码进行对比&#xff0c;判断用户名和密码是否正确&#xff0c;并作出相应提示&…

华为机试题:HJ37 统计每个月兔子的总数(python)

文章目录博主精品专栏导航知识点详解1、input()&#xff1a;获取控制台&#xff08;任意形式&#xff09;的输入。输出均为字符串类型。1.1、input()与list(input())的区别、及其相互转换方法2、print() &#xff1a;打印输出。3、整型int() &#xff1a;将字符串或数字转换为整…

C语言基础(二)—— 常量与变量、数据类型、进位制、关键字、原码反码补码、限定符、字符串格式化输入输出

1. 常量与变量1.1 关键字1.2 数据类型数据类型的作用&#xff1a;编译器预算对象&#xff08;变量&#xff09;分配的内存空间大小。1.3 常量在程序运行过程中&#xff0c;其值不能被改变的量常量一般出现在表达式或赋值语句中整型常量100&#xff0c;200&#xff0c;-100&…

MySQL事务篇

目录​​​​​​​ 一.事务有哪些特性&#xff1f; 二.并行事务会引发什么问题&#xff1f; 脏读 不可重复读 幻读 三.事务的隔离级别有哪些&#xff1f; 一.事务有哪些特性&#xff1f; 原子性&#xff08;Atomicity&#xff09;&#xff1a;一个事务中的所有操作&…

4.数据库安全性

学习过程参考&#xff08;后续章节同&#xff09; 【公开课】数据库系统概论&#xff08;王珊老师&#xff09;&#xff08;完结&#xff09; 《数据库系统概论》思维导图 【专栏必读】数据库系统概论第五版&#xff08;王珊&#xff09;专栏学习笔记目录导航及课后习题答案详…

2023年02月IDE流行度最新排名

点击查看最新IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年02月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…

MySQL的函数

目录 一.分类 聚合函数 概述 格式 操作 数学函数 操作1 操作2 操作3 字符串函数 操作1 操作2 操作3 操作4 日期函数 操作1 操作2 操作3 控制流函数 if逻辑判断语句 case when 语句 窗口函数 介绍 分类 序号函数 开窗聚合函数- SUM,AVG,MIN,MAX 分布函数-…

Java 对象拷贝与转换-org.mapstruct:mapstruct 包(@Mapper、@Mapping)的使用

MapStruct的使用 最近在学习技术时候&#xff0c;发现一个特别好用的包&#xff0c;org.mapstruct:mapstruct&#xff0c;它是专门用来处理 domin 实体类与 model 类的属性映射的 它的优势&#xff1a; 很多项目大量映射的方式通过手动get、set&#xff0c;首先写法很low&…

并发编程 · 基础篇 · android线程那些事

小木箱成长营并发编程系列教程(排期中): 并发编程 基础篇(下) android线程池那些事 并发编程 提高篇(上) Java并发关键字那些事 并发编程 提高篇(下) Java锁安全性那些事 并发编程 高级篇(上) Java内存模型那些事 并发编程 高级篇(下) Java并发BATJ面试之谈 并发编程…

Jupyter notebook——在Anaconda中多个环境下,设置不同的默认打开路径

项目背景&#xff1a;anaconda中搭建了一个python3.6&#xff0c;一个python3.7版本&#xff0c;python3.6环境版本的jupyter notebook默认打开路径设置为&#xff1a;D:\DeepLearning\cv&#xff0c;修改jupyter notebook默认路径见&#xff1a;https://blog.csdn.net/qq_1881…

分享115个图片切换JS特效,总有一款适合您

分享115个图片切换JS特效&#xff0c;总有一款适合您 115个图片切换JS特效下载链接&#xff1a;https://pan.baidu.com/s/1QX7b5LDlY6lBqMVjgBKSwA?pwdk05d 提取码&#xff1a;k05d Python采集代码下载链接&#xff1a;https://wwgn.lanzoul.com/iKGwb0kye3wj jQuery多图…

Appium+Python+pytest自动化测试框架

先简单介绍一下目录&#xff0c;再贴一些代码&#xff0c;代码里有注释Basic目录下写的是一些公共的方法&#xff0c;Data目录下写的是测试数据&#xff0c;image存的是测试失败截图&#xff0c;Log日志文件&#xff0c;Page测试的定位元素&#xff0c;report测试报告&#xff…

Linux从入门到精通

Linux从入门到精通 1. Linux 简介 Linux 内核最初只是由芬兰人林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX&#xff08;可移植操作系统接…

Nacos 注册监听器

文章目录前言项目文件说明pom依赖bootstrap.ymlNacosConfig 配置类监听器实现类-默认实现监听器实现类-json配置处理注册监听器监听器的效果前言 本文主要讨论Nacos作为配置中心时&#xff0c;其中配置内容发生更改时&#xff0c;我们的应用程序能够做的事。 一般使用监听器来…