3.7_流与事件
3.7 流与事件
在CUDA中,流与事件使主机与设备之间的内存复制与内核操作可并发执行。后续的CUDA版本通过扩展流的能力来支持在一个GPU上的多内核并发执行,并支持多GPU并发执行。
CUDA流被用来粗粒度管理多个处理单元的并发执行:
GPU与CPU
在SM处理时可以执行DMA操作的复制引擎
·流处理器簇(SM)
·并发内核
·并发执行的独立GPU
对给定流中的请求操作是串行执行的。狭义上来讲,CUDA流很像CPU的线程,因为一个CUDA流中的操作按顺序执行。
3.7.1 软件流水线
因为只有一个DMA引擎服务于GPU中各种各样的粗粒度硬件资源,应用软件必须对多流中执行的操作进行软件流水线操作。否则,DMA引擎会因为每个流中的不同引擎间的同步而中断并发。6.5节中给出怎样使用流水线技术发挥出CUDA流的全部优势,在11章中会给出更多的范例。
开普勒架构减少了软件流水线流操作的需求,使用英伟达的Hyper-Q技术(在SM3.5中首次使用)实际上消除了软件流水线需求。
3.7.2 流回调
CUDA 5.0引入了另一个CPU/GPU同步机制,这补充了现有的机制,这个新机制可使CPU线程等待,直到流空闲或事件被记录。流回调函数由应用程序提供,被CUDA注册,随后,当流到达cuStreamAddCallback()函数调用的节点时,被CUDA调用。
在流回调期间流执行被挂起,所以,为性能考虑,开发者应该小心确认,在回调时,其他流是否正处于忙碌状态。
3.7.3 NULL流
任何一个异步的内存复制函数都可以使用NULL流作为参数,而且内存复制不会开始,直到GPU之前的操作全部完成。实际上,NULL流是
GPU上所有引擎的集结地。除此之外,所有的流内存复制函数都是异步的。可能在内存复制完成之前把控制权返回给应用程序。NULL流最有用的场合是在不需要使用多流来利用GPU内部的并发性时,它可以解决应用程序的CPU/GPU并发。一旦流操作使用NULL流初始化,应用程序必须使用同步函数(像cuCtxSynchronize()或
CUDAThreadSynchronize())来确保操作在执行下一步之前完成。但是应用程序可能在执行同步之前请求了许多此类操作。例如,应用程序可能执行:
·一次异步主机到设备内存复制;
·一次或多次内核启动;
·一次异步设备到主机内存复制。
在上下文同步之前,cuCtxSynchronize()或CUDAThreadSynchronize()在GPU执行最后一个请求操作后返回。这一方法在小型的不会运行过长时间的内存复制或内核启动中非常有用。CUDA驱动程序使用这段宝贵的时间来写入命令到GPU,并且让CPU执行和GPU命令处理重叠进行,以提升性能。
注意 即使在CUDA 1.0中,内核启动也是异步的。对任意的内核,在没有显式指定流的情况下,NULL流被隐式指定。
3.7.4 事件
CUDA事件表示了另一个同步机制。同CUDA流一同引入,记录CUDA事件是CUDA流中应用程序跟踪进度的一个方式。当之前的所有CUDA流的操作执行结束后,全部的CUDA事件通过写入一个共享同步内存位置而起作用[1]。查询CUDA事件会引发驱动程序窥探这一内存位置,报告事件是否被记录。同步CUDA事件会引发驱动程序等待,直到事件被记录。
可选择的是,CUDA事件同样可以写入一个从硬件高分辨率计时器中派生出的时间戳。基于事件的计时可以比基于CPU的计时更加准确,尤其对小型的操作,因为这不会受限于伪非相关事件(例如页错误或网络流量),但这类事件会影响CPU系统时钟计时。系统时钟时间是无法更改的,因为它是最终用户看到的时间的更好近似,因此CUDA事件计时最好使用在生产环境中的性能调优中[2]。
CUDA事件计时最好与NULL流一同使用。使用这一规则的原因类似于CPU上的串行指令RDTSC(读时间戳计数器):正像CPU是一个超标量处理器,可以同时处理许多指令,GPU也可在多个流里同时运行。在没有显式序列化的情况下,一个计时操作可能无意的包含了本不想被计时的操作,也可能排除了一些应该被包含进的操作。正如RDTSC经常
使用的技巧,我们为计时提供足够的工作,所以计时本身的执行时间可以忽略。
随机的CUDA事件可能会引发一次中断,硬件会为中断发出信号,使驱动程序执行一次所谓的阻塞等待。当驱动程序等待GPU的同时阻塞等待会挂起等待的CPU线程,节省CPU时钟周期和功率。在阻塞等待机制可用之前,CUDA开发者普遍的抱怨CUDA驱动程序通过轮询内存位置让整个CPU核心空转来等待GPU。但与此同时,阻塞等待可能由于处理中断的额外开销将消耗更多的时间,所以很多应用程序可能还是希望使用默认的轮询行为。
[1] 指定NULL流给cuEventRecord()或cudaEventRecord()函数,意味着事件将会在GPU处理完所有待处理操作后被记录。
[2] 此外,用于计时的CUDA事件不能在其他操作中使用,最近的CUDA版本允许“禁用计时”特性,便于在其他操作中使用CUDA事件,例如跨设备同步。