CUDA从入门到放弃(七):流( Streams)

CUDA从入门到放弃(七):流( Streams)

应用程序通过流来管理并发操作,流是一系列按顺序执行的命令。不同的流可能无序或并发地执行命令,但此行为并不保证。流上的命令在依赖关系满足时执行,这些依赖可能来自同一流或其他流。同步调用(synchronize call)可以确保所有启动的命令已完成。

任何 CUDA 操作都存在于某个 CUDA 流中,要么是默认流(default stream),也称为空流(null stream),要么是明确指定的非空流。

1 流的基本概念

1-1 流的创建与销毁 Creation and Destruction of Streams

CUDA 流的定义、产生与销毁

cudaStream_t stream_1;
cudaStreamCreate(&stream_1); // 注意要传流的地址
cudaStreamDestroy(stream_1);

示例:

cudaStream_t stream[2];
for (int i = 0; i < 2; ++i)cudaStreamCreate(&stream[i]);
float* hostPtr;
cudaMallocHost(&hostPtr, 2 * size);for (int i = 0; i < 2; ++i) {cudaMemcpyAsync(inputDevPtr + i * size, hostPtr + i * size,size, cudaMemcpyHostToDevice, stream[i]);MyKernel <<<100, 512, 0, stream[i]>>>(outputDevPtr + i * size, inputDevPtr + i * size, size);cudaMemcpyAsync(hostPtr + i * size, outputDevPtr + i * size,size, cudaMemcpyDeviceToHost, stream[i]);
}for (int i = 0; i < 2; ++i)cudaStreamDestroy(stream[i]);

1-2 默认流 Default Stream

my_kernel<<<N_grid, N_block>>>(函数参数);
my_kernel<<<N_grid, N_block, N_shared>>>(函数参数);
my_kernel<<<N_grid, N_block, N_shared, stream_id>>>(函数参数);

如果用第一种调用方式,说明核函数没有使用动态共享内存,而且在默认流中执行;
如果用第二种调用方式,说明核函数在默认流中执行,但使用了 N_shared 字节的动态共享内存;
如果用第三种调用方式,则说明核函数在编号为stream_id 的 CUDA 流中执行,而且使用了 N_shared 字节的动态共享内存。

在使用非空流但不使用动态共享内存的情况下,必须使用上述第三种调用方式,并将 N_shared 设置为零:

my_kernel<<<N_grid, N_block, 0, stream_id>>>(函数参数); // 正确

1-3 流状态查询

为了实现不同 CUDA 流之间的并发,主机在向某个 CUDA 流中发布一系列命令之后必须马上获得程序的控制权,不用等待该 CUDA 流中的命令在设备中执行完毕。这样,就可以通过主机产生多个相互独立的 CUDA 流。

为了检查一个 CUDA 流中的所有操作是否都在设备中执行完毕, CUDA 运行时 API 提
供了如下函数:

__host__cudaError_t cudaLaunchHostFunc(cudaStream_t stream, cudaHostFn_t fn, void *userData)

cudaDeviceSynchronize()会等待直到所有主机线程的所有流中的所有前面命令都已完成。

__host__cudaError_t cudaStreamSynchronize(cudaStream_t stream)

cudaStreamSynchronize()接受一个流作为参数,并等待直到给定流中的所有前面命令都已完成。它可用于同步主机与特定流,同时允许其他流继续在设备上执行。

__host____device__cudaError_t cudaStreamWaitEvent (cudaStream_t stream, cudaEvent_t event, unsigned int flags)

cudaStreamWaitEvent()接受一个流和一个事件作为参数,并使得所有在调用cudaStreamWaitEvent()之后添加到给定流的命令延迟其执行,直到给定事件已完成。

cudaError_t cudaStreamQuery(cudaStream_t stream);

函数 cudaStreamQuery 不会阻塞主机,只是检查 CUDA 流 stream 中的所有操作是否都执行完毕。若是,返回 cudaSuccess,否则返回 cudaErrorNotReady。

2 重叠执行 Overlapping Behavior

2-1 在默认流中重叠主机和设备计算

虽然同一个 CUDA 流中的所有 CUDA 操作都是顺序执行的,但依然可以在默认流中重叠主机和设备的计算。

示例:

cudaMemcpy(d_x, h_x, M, cudaMemcpyHostToDevice);
cudaMemcpy(d_y, h_y, M, cudaMemcpyHostToDevice);
sum<<<grid_size, block_size>>>(d_x, d_y, d_z, N);
cudaMemcpy(h_z, d_z, M, cudaMemcpyDeviceToHost);

cudaMemcpy(d_x, h_x, M, cudaMemcpyHostToDevice); 

sum<<<grid_size, block_size>>>(d_x, d_y, d_z, N);

顺序依次执行。
但是当主机发出

sum<<<grid_size, block_size>>>(d_x, d_y, d_z, N);

命令之后,不会等待该命令执行完毕,而会立刻得到程序的控制权。主机紧接着会发出从设备到主机传输数据的命令

cudaMemcpy(h_z, d_z, M, cudaMemcpyDeviceToHost);

该命令会在CUDA流中等待前一个 CUDA 操作(即核函数的调用)执行完毕才会开始执行。
因此,主机在发出核函数调用的命令之后,可以执行主机中的某个计算任务,那么主机就会在设备执行核函数的同时去进行一些计算。这样,主机和设备就可以同时进行计算。

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

尽管在单个默认流中确实可以实现主机计算与设备计算的并行处理,但要实现多个核函数之间的并行执行,则必须借助多个CUDA流。这是因为,在相同的CUDA流内,CUDA操作在GPU设备上是按照顺序执行的。因此,同一个CUDA流内的核函数也必须在设备上依次执行,即便主机在发出每一个核函数调用指令后都立即恢复了程序的控制权。这样的设计确保了操作的顺序性和一致性,但同时也限制了并行度的提升。通过使用多个CUDA流可以有效地将不同的核函数任务分配到不同的执行流中,从而实现更高程度的并行计算,提升整体计算性能。

为了实现核函数执行与数据传输的并发(重叠),必须确保这两个操作在不同的非默认流中执行,且数据传输应使用 cudaMemcpyAsync 函数,这是 cudaMemcpy 的异步版本。cudaMemcpyAsync 通过 GPU 中的 DMA 实现直接内存访问,无需主机参与。

异步传输函数 cudaMemcpyAsync:

cudaError_t cudaMemcpyAsync(  void *dst,                    // 目标内存地址  const void *src,             // 源内存地址  size_t count,                // 传输的字节数  enum cudaMemcpyKind kind,   // 传输方向(主机到设备、设备到主机等)  cudaStream_t stream          // 所在的流  
);

这个函数与同步版本的 cudaMemcpy 相比,仅多出一个参数:流(stream)。通过将数据传输操作分配给特定流,并允许核函数在另一个流中执行,可以实现数据传输和核函数执行的并发执行,从而提高程序的整体性能。

示例:

for (int i = 0; i < 2; ++i)cudaMemcpyAsync(inputDevPtr + i * size, hostPtr + i * size,size, cudaMemcpyHostToDevice, stream[i]);
for (int i = 0; i < 2; ++i)MyKernel<<<100, 512, 0, stream[i]>>>(outputDevPtr + i * size, inputDevPtr + i * size, size);
for (int i = 0; i < 2; ++i)cudaMemcpyAsync(hostPtr + i * size, outputDevPtr + i * size,size, cudaMemcpyDeviceToHost, stream[i]);

3 主机函数(回调函数)Host Functions (Callbacks)

通过 cudaLaunchHostFunc(),可以在流的任意位置插入主机函数的调用。此函数会在流中所有先前命令完成后在主机上执行。

以下示例在每个流执行主机到设备内存复制、内核启动和设备到主机内存复制后,将主机函数 MyCallback 添加到流中。一旦设备到主机的内存复制完成,该回调函数将在主机上执行。

void CUDART_CB MyCallback(void *data){printf("Inside callback %d\n", (size_t)data);
}
...
for (size_t i = 0; i < 2; ++i) {cudaMemcpyAsync(devPtrIn[i], hostPtr[i], size, cudaMemcpyHostToDevice, stream[i]);MyKernel<<<100, 512, 0, stream[i]>>>(devPtrOut[i], devPtrIn[i], size);cudaMemcpyAsync(hostPtr[i], devPtrOut[i], size, cudaMemcpyDeviceToHost, stream[i]);cudaLaunchHostFunc(stream[i], MyCallback, (void*)i);
}

请注意,排入流的主机函数应避免直接或间接调用 CUDA API,以免发生死锁。

4 流优先级

在创建流时,可以使用 cudaStreamCreateWithPriority() 函数指定流的相对优先级。通过 cudaDeviceGetStreamPriorityRange() 函数,可以获取允许的优先级范围,该范围按从最高优先级到最低优先级的顺序排列。在运行时,高优先级流中的待处理任务将优先于低优先级流中的待处理任务执行。

以下代码示例获取当前设备的允许优先级范围,并使用可用的最高和最低优先级创建流。

// get the range of stream priorities for this device
int priority_high, priority_low;
cudaDeviceGetStreamPriorityRange(&priority_low, &priority_high);
// create streams with highest and lowest available priorities
cudaStream_t st_high, st_low;
cudaStreamCreateWithPriority(&st_high, cudaStreamNonBlocking, priority_high);
cudaStreamCreateWithPriority(&st_low, cudaStreamNonBlocking, priority_low);

参考资料
1 CUDA编程入门
2 CUDA编程入门极简教程
3 CUDA C++ Programming Guide
4 CUDA C++ Best Practices Guide
5 NVIDIA CUDA初级教程视频
6 CUDA专家手册 [GPU编程权威指南]
7 CUDA并行程序设计:GPU编程指南
8 CUDA C编程权威指南

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

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

相关文章

【设计模式】抽象工厂模式详解

抽象工厂 是一种为访问类提供一个创建一族相关或者相互依赖对象的接口&#xff0c;且访问类无须指定所要产品的具体类就可以得到同一族的不同等级的产品模式结构 抽象工厂模式是工厂方法模式的升级版本&#xff0c;工厂方法模式只生产一个等级的产品&#xff0c;而抽象工厂模式…

Android room 在dao中不能使用挂起suspend 否则会报错

错误&#xff1a; Type of the parameter must be a class annotated with Entity or a collection/array of it. kotlin.coroutines.Continuation<? super kotlin.Unit> $completion); 首先大家检查一下几个点 一、kotlin-kapt 二、 是否引入了 room-ktx 我是2024年…

Vue CLI 配置与 Nginx 反向代理:无缝连接前后端API通信

在Web开发中&#xff0c;Vue.js用于构建单页面应用。在前后端分离的架构中&#xff0c;Vue应用通常需要通过API与后端服务器交互。所以&#xff0c;Vue CLI工具提供了便捷的开发环境配置选项&#xff0c;而Nginx则在生产环境中扮演关键角色&#xff0c;确保API请求正确路由至后…

[flink] flink macm1pro 快速使用从零到一

文章目录 快速使用 快速使用 打开 https://flink.apache.org/downloads/ 下载 flink 因为书籍介绍的是 1.12版本的&#xff0c;为避免不必要的问题&#xff0c;下载相同版本 解压 tar -xzvf flink-1.11.2-bin-scala_2.11.tgz启动 flink ./bin/start-cluster.sh打开 flink web…

LeetCode 面试经典150题 242.有效的字母异位词

题目&#xff1a; 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 思路&#xff1a;hash表&#xff0c;可以用int数组代替 代码&#x…

【每日一题】盛水容器

问题描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容…

JavaScript中作用域与闭包深入解析

函数中的作用域 对这些问题的最常见的回答是&#xff0c;JavaScript 拥有基于函数的作用域。也就是&#xff0c;你声明的每一个函数都为自己创建了一个气泡&#xff0c;而且没有其他的结构可以创建它们自己的作用域气泡。但是就像我们一会儿将会看到的&#xff0c;这不完全正确…

vue创建项目报错Fail to check for updates

网上查了文章说更换淘宝镜像地址啥的 改了地址后依然报错显示Fail to check for updates 并且装包时报错Failed to get response from https://registry.npmmirror.com/binary-mirror-config 既然又是淘宝镜像问题&#xff0c;直接干脆不用淘宝的地址 npm config set regis…

iPhone的iOS系统:定义移动智能体验,引领科技潮流之巅

来自&#xff1a;dlshuhua.com/post/83721.html 在移动智能设备领域&#xff0c;iPhone一直以其出色的性能和独特的用户体验脱颖而出。而这一切的背后&#xff0c;离不开其强大的操作系统——iOS。iOS系统不仅为iPhone提供了强大的性能支持&#xff0c;更通过不断创新和升级&a…

蓝桥杯备考随手记: 数位分解

1. 什么是数位分解 数位分解是将一个数拆分成它的各个数位的过程。每个数位代表了数字在该位上的权重。 例如&#xff0c;对于整数12345&#xff0c;数位分解可以得到以下结果&#xff1a; 万位&#xff1a;1千位&#xff1a;2百位&#xff1a;3十位&#xff1a;4个位&#…

产品经理的自我修养

点击下载《产品经理的自我修养》 1. 前言 在产品领域取得成功的关键在于持续的激情。只有保持热情不减,我们才能克服各种困难,打造出卓越的产品。 如果你真心渴望追求产品之路,我强烈建议你立即行动起来,亲自参与实际的产品创作。无论是建立一个网站、创建一个社群,还是…

Dubbo 负载均衡算法说明

https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/ 在集群负载均衡时&#xff0c;Dubbo 提供了多种均衡策略&#xff0c;缺省为 weighted random 基于权重的随机负载均衡策略。 具体实现上&#xff0c;Dubbo 提供的是客户端负载均衡&#xff0c;即由 …

【前端学习——js篇】4.浅拷贝与深拷贝

具体可见https://github.com/febobo/web-interview 4.浅拷贝与深拷贝 ①栈内存与堆内存 栈内存&#xff08;Stack Memory&#xff09; 栈内存用于存储基本类型的变量和引用类型的变量引用&#xff08;即指向堆内存中实际数据的指针&#xff09;。当一个函数被调用时&#xf…

Mysql的日志管理,备份与回复

目录 一、Mysql日志管理 1、日志的默认位置及配置文件 2、日志分类 2.1错误日志 2.2通用查询日志 2.3二进制日志 2.4慢查询日志 2.5中继日志 3、日志配置 4、日志查询 4.1查询通用日志是否开启 4.2查询二进制日志是否开启 4.3查看慢查询日志是否开启 4.4查询慢查…

Vivado Lab Edition

Vivado Lab Edition 是完整版 Vivado Design Suite 的独立安装版本 &#xff0c; 包含在生成比特流后对赛灵思 FPGA 进行编程和 调试所需的所有功能。通常适用于在如下实验室环境内进行编程和调试&#xff1a; 实验室环境中的机器所含磁盘空间、内存和连 接资源较少。Vivad…

python数据实时传给unity工程并绘制出来

python # 服务器端代码 import socket import random import struct import time# 创建一个服务器Socket server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 监听的地址和端口 host 127.0.0.1 port 12345# 绑定地址和端口 server_socket.bind((host, port…

纯分享万岳外卖跑腿系统客户端源码uniapp目录结构示意图

系统买的是商业版&#xff0c;使用非常不错有三端uniapp开源代码&#xff0c;自从上次分享uniapp后有些网友让我分享下各个端的uniapp下的各个目录结构说明 我就截图说以下吧&#xff0c;

【Java - 框架 - Lombok】(1) 普通Java项目通过Lombok+Logback完成日志的创建使用 - 快速上手

普通Java项目通过"Lombok""Logback"完成日志的创建使用 - 快速上手&#xff1b; 步骤A 说明 创建"Maven"项目&#xff1b; 图片 步骤B 说明 添加相关依赖项&#xff1b; 图片 代码 <!-- "Lombok"依赖项--> <dependency>&…

c++核心学习--继承2

4.6.7多继承语法 4.6.8菱形继承 利用虚继承解决菱形继承的问题&#xff1a;继承之前加上关键字virtual变为虚继承

经纬恒润RTaW-Pegase:车载网络通信建模与时间特性分析工具

▎RTaW简介 RTaW-Pegase是由法国国家信息与自动化研究所&#xff08;INRIA&#xff09;旗下的RTaW公司开发的产品。它主要用于构建和优化汽车、航空航天以及工业领域的通信网络&#xff0c;包括时间敏感网络&#xff08;TSN&#xff09;、CAN&#xff08;FD&#xff0c;XL&…