《CUDA编程:基础与实践》读书笔记(4):CUDA流
1. CUDA流
一个CUDA流指的是由主机发出的在一个设备中执行的CUDA操作序列。除主机端发出的流之外,还有设备端发出的流,但本文不考虑后者。一个CUDA流中的各个操作按照主机发布的次序执行;但来自两个不同CUDA流的操作不一定按照某个次序执行,有可能是并发或者交错地执行。
任何CUDA操作都存在于某个CUDA流中,如果没有明确指定CUDA流,那么所有CUDA操作都是在默认流中执行的。非默认CUDA流由cudaStream_t
类型的变量表示,它由如下CUDA运行时API产生与销毁:
cudaError_t cudaStreamCreate(cudaStream_t* pStream);
cudaError_t cudaStreamDestroy(cudaStream_t stream);
为了检查CUDA流中的所有操作是否都在设备中执行完毕,可以使用如下函数:
//阻塞主机直到stream中的所有操作都执行完毕
cudaError_t cudaStreamSynchronize(cudaStream_t stream);
//不阻塞主机,只检查stream中的所有操作是否都执行完毕,若是则返回cudaSuccess,否则返回cudaErrorNotReady
cudaError_t cudaStreamQuery(cudaStream_t stream);
为了产生多个相互独立的CUDA流、实现不同CUDA流之间的并发,主机在向某个CUDA流中发布命令后必须马上获取程序控制权,不等待该CUDA流中的命令在设备中执行完毕。下文将介绍主机如何在向某个CUDA流发布命令后马上取得控制权。此外,也可以在主机端使用多个线程控制多个CUDA流。
2. 核函数与主机的重叠执行
下面是默认CUDA流中数组相加的例子:
cudaMemcpy(d_x, h_x, M, cudaMemcpyHostToDevice);
cudaMemcpy(d_y, h_y, M, cudaMemcpyHostToDevice);
add<<<grid_size, block_size>>>(d_x, d_y, d_z);
cudaMemcpy(h_z, d_z, M, cudaMemcpyDeviceToHost);
从设备的角度看,以上4个CUDA操作将在默认CUDA流中按顺序依次执行。从主机的角度看,数据传输是同步的(或者说是阻塞的),比如说主机在执行前两个cudaMemcpy
语句时,会等待该命令执行完毕再继续往下走,所以在进行数据传输时,主机是闲置的,不能进行其它操作。不同的是,核函数的启动是异步的(或者说是非阻塞的),意思是主机发出调用核函数的命令后,不会等待命令执行完毕,而会立刻取得程序控制权,然后紧接着发出最后一个cudaMemcpy
命令,但是该命令不会立即被执行,因为这是默认流中的CUDA操作,必须等待前一个CUDA操作(即核函数的调用)执行完毕才会开始执行。
根据上述分析可知,主机在发出核函数调用命令后会立刻继续执行接下来的命令。如果下一条命令是主机的某个计算任务,那么就可以实现核函数与主机计算任务的并行计算。
3. 核函数与核函数的重叠执行
因为同一个CUDA流中的CUDA操作在设备中是顺序执行的,所以要实现多个核函数之间的并行就必须使用多个CUDA流。在使用的多个CUDA流中可以有一个默认流,但此时各个流之间并不完全独立,本文不讨论这种情况,只讨论使用多个非默认流的情况。在非默认流中调用核函数时,执行配置必须包含一个流对象,一个名为my_kernel(...)
的核函数只能用如下三种调用方式之一:
//N_grid是网格大小,最一般的情形是一个dim3类型的结构体,简单情况下可以是一个整数
//N_block是线程块大小,最一般的情形是一个dim3类型的结构体,简单情况下可以是一个整数
//N_shared是核函数中使用的动态共享内存的字节数,如果没有则设为0
//stream是cudaStream_t类型的CUDA流对象
my_kernel<<<N_grid, N_block>>>(...);
my_kernel<<<N_grid, N_block, N_shared>>>(...);
my_kernel<<<N_grid, N_block, N_shared, stream>>>(...);
下面的例子简单展示了如何使用非默认CUDA流重叠执行多个核函数:
#include "cuda_runtime.h"
void __global__ my_kernel()
{
// do some calculations
}
int main(void)
{
const int NUM_STREAMS = 16;
const int block_size = 128;
const int grid_size = 8;
cudaStream_t streams[NUM_STREAMS];
for (int n = 0; n < NUM_STREAMS; ++n)
{
cudaStreamCreate(&(streams[n]));
}
for (int n = 0; n < NUM_STREAMS; ++n)
{
my_kernel<<<grid_size, block_size, 0, streams[n]>>>();
}
for (int n = 0; n < NUM_STREAMS; ++n)
{
cudaStreamDestroy(streams[n]);
}
return 0;
}
利用CUDA流并发执行多个核函数可以提升GPU硬件的利用率,减少闲置的SM,从而整体上获得性能提升。但当所有CUDA流中对应核函数的线程数总和超过一定阈值后,再增加CUDA流的数量就不会带来更高的加速比了,反而可能使程序的性能下降。制约加速比的因素是GPU的计算资源。
4. 核函数与数据传输的重叠执行
要实现核函数与数据传输的并发,必须让这两个操作处于不同的非默认流,而且数据传输必须使用cudaMemcpy
的异步版本,即cudaMemcpyAsync
函数。如果使用同步的数据传输函数,主机向一个流发出输出传输命令后就必须等待数据传输完毕,这样核函数与数据传输的重叠也就无法实现。异步传输函数的原型是:
cudaError_t cudaMemcpyAsync(void *dst, const void *src, size_t count, enum cudaMemcpyKind kind, cudaStream_t stream);
在使用异步数据传输函数时,需要将主机内存定义为不可分页内存,这样在程序运行期间操作系统就不会改变主机内存的物理地址。如果给cudaMemcpyAsync
函数传入的主机内存是可分页内存,那么函数就会退化到cudaMemcpy
,从而导致同步传输,无法达到核函数与数据传输重叠执行的效果。不可分页主机内存的分配与释放可以用如下函数:
cudaError_t cudaMallocHost(void **ptr, size_t size);
cudaError_t cudaFreeHost(void *ptr);
下面给出一个使用CUDA流重叠执行核函数和数据传输的例子:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
const int N = 1 << 22;
const int M = sizeof(float) * N;
const int NUM_STREAMS = 64;
cudaStream_t streams[NUM_STREAMS];
void __global__ add(const float* x, const float* y, float* z, int N)
{
const int n = blockDim.x * blockIdx.x + threadIdx.x;
if (n < N)
{
z[n] = x[n] + y[n];
}
}
int main(void)
{
float *h_x, *h_y, *h_z;
cudaMallocHost(&h_x, M);
cudaMallocHost(&h_y, M);
cudaMallocHost(&h_z, M);
for (int n = 0; n < N; ++n)
{
h_x[n] = 1.23f;
h_y[n] = 2.34f;
}
float *d_x, *d_y, *d_z;
cudaMalloc(&d_x, M);
cudaMalloc(&d_y, M);
cudaMalloc(&d_z, M);
for (int i = 0; i < NUM_STREAMS; i++)
{
cudaStreamCreate(&(streams[i]));
}
int N1 = N / NUM_STREAMS;
int M1 = M / NUM_STREAMS;
for (int i = 0; i < NUM_STREAMS; i++)
{
int off = i * N1;
cudaMemcpyAsync(d_x + off, h_x + off, M1, cudaMemcpyHostToDevice, streams[i]);
cudaMemcpyAsync(d_y + off, h_y + off, M1, cudaMemcpyHostToDevice, streams[i]);
int block_size = 128;
int grid_size = (N1 - 1) / block_size + 1;
add<<<grid_size, block_size, 0, streams[i]>>>(d_x + off, d_y + off, d_z + off, N1);
cudaMemcpyAsync(h_z + off, d_z + off, M1, cudaMemcpyDeviceToHost, streams[i]);
}
for (int i = 0; i < NUM_STREAMS; i++)
{
cudaStreamDestroy(streams[i]);
}
cudaFreeHost(h_x);
cudaFreeHost(h_y);
cudaFreeHost(h_z);
cudaFree(d_x);
cudaFree(d_y);
cudaFree(d_z);
return 0;
}
《CUDA编程:基础与实践》读书笔记(4):CUDA流的更多相关文章
- 《Java并发编程的艺术》读书笔记:二、Java并发机制的底层实现原理
二.Java并发机制底层实现原理 这里是我的<Java并发编程的艺术>读书笔记的第二篇,对前文有兴趣的朋友可以去这里看第一篇:一.并发编程的目的与挑战 有兴趣讨论的朋友可以给我留言! 1. ...
- 《jQuery基础教程》读书笔记
最近在看<jQuery基础教程>这本书,做了点读书笔记以备回顾,不定期更新. 第一章第二章比较基础,就此略过了... 第三章 事件 jQuery中$(document).ready()与j ...
- 《Java并发编程的艺术》读书笔记:一、并发编程的目的与挑战
发现自己有很多读书笔记了,但是一直都是自己闷头背,没有输出,突然想起还有博客圆这么个好平台给我留着位置,可不能荒废了. 此文读的书是<Jvava并发编程的艺术>,方腾飞等著,非常经典的一本 ...
- cuda编程基础
转自: http://blog.csdn.net/augusdi/article/details/12529247 CUDA编程模型 CUDA编程模型将CPU作为主机,GPU作为协处理器(co-pro ...
- Java并发编程实践读书笔记(2)多线程基础组件
同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...
- Java并发编程实践读书笔记(5) 线程池的使用
Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...
- Java并发编程实践(读书笔记) 任务执行(未完)
任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元. 任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...
- Java并发编程实践读书笔记(1)线程安全性和对象的共享
2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...
- Java并发编程实践读书笔记(4)任务取消和关闭
任务的取消 中断传递原理 Java中没有抢占式中断,就是武力让线程直接中断. Java中的中断可以理解为就是一种简单的消息机制.某个线程可以向其他线程发送消息,告诉你“你应该中断了”.收到这条消息的线 ...
- Java并发编程实践读书笔记(3)任务执行
类似于Web服务器这种多任务情况时,不可能只用一个线程来对外提供服务.这样效率和吞吐量都太低. 但是也不能来一个请求就创建一个线程,因为创建线程的成本很高,系统能创建的线程数量是有限的. 于是Exec ...
随机推荐
- 海外SRC信息收集工具
海外SRC信息收集 子域名爆破工具:bbot,subfinder 相关测评:https://blog.blacklanternsecurity.com/p/subdomain-enumerat ...
- 4年经验来面试20K的测试岗,连基础都不会,还不如招应届生。
公司前段时间缺人,也面了不少测试,结果竟然没有一个合适的.一开始瞄准的就是中级的水准,也没指望来大牛,提供的薪资在10-20k,面试的人很多,但平均水平很让人失望.看简历很多都是3.4年工作经验,但面 ...
- 3.11 Linux删除空目录(rmdir命令)
和 mkdir 命令(创建空目录)恰好相反,rmdir(remove empty directories 的缩写)命令用于删除空目录,此命令的基本格式为: [root@localhost ~]# rm ...
- 如何在原生鸿蒙APP中使用RN的bundle包
一.创作背景 上一篇博客中,我给大家分享了如何创建一个RN的项目,并且解决了其中的问题点,成功打出了Bundle包.接下来就是我给大家分享一下,如何在原生鸿蒙项目中使用那个Bundle包,这一篇分享完 ...
- 基于Java+SpringBoot心理测评心理测试系统功能实现三
一.前言介绍: 1.1 项目摘要 心理测评和心理测试系统在当代社会中扮演着越来越重要的角色.随着心理健康问题日益受到重视,心理测评和心理测试系统作为评估个体心理状态.诊断心理问题.制定心理治疗方案的工 ...
- 【一步步开发AI运动小程序】十六、AI运动识别中,如何判断人体站位?
[云智AI运动识别小程序插件],可以为您的小程序,赋于人体检测识别.运动检测识别.姿态识别检测AI能力.本地原生识别引擎,无需依赖任何后台或第三方服务,有着识别速度快.体验佳.扩展性强.集成快.成本低 ...
- Golang之工作区workspace
快速开始 创建工作区 写一个最简单的基础项目实际演练一下 Go workspace. 首先,创建 workspace 工作区. $: mkdir workspace $: cd workspace $ ...
- 微服务-SpringBoot
基础知识 微服务主旨就是将一个大型系统拆分为多个小型服务. 多个服务之间可以是异构的.单体服务在大型项目下很难维护. 智能端点与哑管道:就是消息之间通信只传送消息,而不做校验. SpringCloud ...
- EasyExcel => EasyExcel-Plus => FastExcel
目录 什么是 FastExcel 主要特性 适用场景 结论 导航 快速开始 EasyExcel 与 FastExcel 的区别 EasyExcel 如何升级到 FastExcel 1. 修改依赖 2. ...
- ant 表格中的分页属性
组件中添加 属性 :pagination="paginationProps" 定义paginationprops const paginationProps = reactive( ...