一、Stream的概念
用到CUDA的程序一般需要处理海量的数据,内存带宽经常会成为主要的瓶颈。在Stream的帮助下,CUDA程序可以有效地将内存读取和数值运算并行,从而提升数据的吞吐量。
Cuda stream是指一堆异步的cuda操作,他们按照host代码调用的顺序执行在device上。
典型的cuda编程模式我们已经熟知了:
· 将输入数据从host转移到device
· 在device上执行kernel
· 将结果从device上转移回host
Cuda Streams
所有的cuda操作(包括kernel执行和数据传输)都显式或隐式的运行在stream中,stream也就两种类型,分别是:
· 隐式声明stream(NULL stream)
· 显示声明stream(non-NULL stream)
异步且基于stream的kernel执行和数据传输能够实现以下几种类型的并行:
· Host运算操作和device运算操作并行
· Host运算操作和host到device的数据传输并行
· Host到device的数据传输和device运算操作并行
· Device内的运算并行
二、Stream的使用
由于GPU和CPU不能直接读取对方的内存,CUDA程序一般会有一下三个步骤:1)将数据从CPU内存转移到GPU内存(HtoD),2)GPU进行运算并将结果保存在GPU内存(DtoD),3)将结果从GPU内存拷贝到CPU内存(DtoH)。
如果不做特别处理,那么CUDA会默认只使用一个Stream(Default Stream)。在这种情况下,刚刚提到的三个步骤就如菊花链般蛋疼地串联,必须等一步完成了才能进行下一步。是不是很别扭?
NVIDIA家的GPU有一下很不错的技能(不知道是不是独有):
数据拷贝和数值计算可以同时进行。
两个方向的拷贝可以同时进行(GPU到CPU,和CPU到GPU),数据如同行驶在双向快车道。
但同时,这数据和计算的并行也有一点合乎逻辑的限制:进行数值计算的kernel不能读写正在被拷贝的数据。
Stream正是帮助我们实现以上两个并行的重要工具。基本的概念是:
将数据拆分称许多块,每一块交给一个Stream来处理。
每一个Stream包含了三个步骤:1)将属于该Stream的数据从CPU内存转移到GPU内存,2)GPU进行运算并将结果保存在GPU内存,3)将该Stream的结果从GPU内存拷贝到CPU内存。
所有的Stream被同时启动,由GPU的scheduler决定如何并行。
在这样的骚操作下,假设我们把数据分成A,B两块,各由一个Stream来处理。A的数值计算可以和B的数据传输同时进行,而A与B的数据传输也可以同时进行。由于第一个Stream只用到了数据A,而第二个Stream只用到了数据B,“进行数值计算的kernel不能读写正在被拷贝的数据”这一限制并没有被违反。效果如下:
下图表示数据在CPU内存与GPU内存转移的起始时间、持续时间、使用的GPU、CUDA流的名称
三、总结
使用多个Stream令数据传输和计算并行,可比只用Default Stream增加相当多的吞吐量。在需要处理海量数据,Stream是一个十分重要的工具。