CUDA运行时 Runtime

一.异步并发执行

CUDA将以下操作公开为可以彼此并发操作的独立任务:

主机计算;

设备计算;

从主机到设备的内存传输;

从设备到主机的存储器传输;

在给定设备的存储器内的存储器传输;

设备之间的内存传输。

这些操作之间实现的并发级别将取决于设备的功能集和计算能力,如下所述。

二.  主机和设备之间的并发执行

在设备完成请求的任务之前,通过异步库函数将控制权返回给主机线程,可以促进并发主机执行。使用异步调用,当适当的设备资源可用时,许多设备操作可以一起排队,由CUDA驱动程序执行。这减轻了主机线程管理设备的大部分责任,让它可以自由地执行其他任务。以下设备操作相对于主机是异步的:

内核启动;

单个设备内存中的内存副本;

64 KB或更小的内存块从主机到设备的内存拷贝;

由以Async为后缀的函数执行的内存拷贝;

内存设置函数调用。

程序员可以通过将CUDA启动阻塞环境变量设置为1,全局禁用系统上运行的所有CUDA应用程序的内核启动异步性。此功能仅用于调试目的,不应用作使生产软件可靠运行的方法。              如果通过探查器(Nsight,Visual profiler)收集硬件计数器,则内核启动是同步的,除非启用了并发内核评测。如果异步内存副本涉及未页锁定的主机内存,则它们也将是同步的。

三.  并发内核执行

一些计算能力为2.x及更高的设备可以同时执行多个内核。应用程序可以通过检查concurrentKernels设备属性(请参阅设备枚举)来查询此功能,对于支持此功能的设备,该属性等于1。

设备可以并发执行的最大内核启动次数取决于其计算能力,如表15所示。

一个CUDA上下文中的内核不能与另一个CUDA上下文中的内核同时执行。

使用许多纹理或大量本地内存的内核不太可能与其他内核同时执行。

四.  数据传输与内核执行的重叠

有些设备可以在内核执行的同时执行到GPU或从GPU执行的异步内存复制。应用程序可以通过检查asyncEngineCount设备属性(请参阅设备枚举)来查询此功能,对于支持此功能的设备,该属性大于零。如果复制涉及主机内存,则必须将其页锁定。

还可以在内核执行(在支持concurrentKernels设备属性的设备上)和/或与设备之间的副本(对于支持asyncEngineCount属性的设备)同时执行设备内复制。使用标准内存复制功能启动设备内复制,目标和源地址位于同一设备上。

五.  并发数据传输

一些计算能力为2.x及更高的设备可以在设备之间重叠拷贝。应用程序可以通过检查asyncEngineCount设备属性(请参阅设备枚举)来查询此功能,对于支持此功能的设备,该属性等于2。为了重叠,传输中涉及的任何主机内存都必须被页锁定。             

六.  线程流

应用程序通过流管理上述并发操作。流是按顺序执行的命令序列(可能由不同的主机线程发出)。另一方面,不同的流可能会执行它们的命令,彼此不按顺序执行,或者同时执行;这种行为没有保证,因此不应依赖于正确性(例如,内核间通信未定义)。流上发出的命令可以在满足命令的所有依赖项时执行。依赖项可以是以前在同一流上启动的命令,也可以是来自其他流的依赖项。同步调用的成功完成保证所有启动的命令都已完成。

七.  创造与销毁

流是通过创建一个流对象并将其指定为内核启动序列和主机<->设备内存副本的流参数来定义的。下面的代码示例创建两个流并在页锁定内存中分配float的数组hostPtr。

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]);

}

每个流将其输入数组hostPtr的部分复制到设备内存中的数组inputDevPtr,通过调用MyKernel()在设备上处理inputDevPtr,并将结果outputDevPtr复制回hostPtr的相同部分。重叠行为描述了在本例中,根据设备的能力,流如何重叠。请注意,hostPtr必须指向页锁定的主机内存,否则将发生任何重叠。

通过调用cudaStreamDestroy()释放流。

for (int i = 0; i < 2; ++i)

cudaStreamDestroy(stream[i]);

如果在调用cudaStreamDestroy()时设备仍在流中工作,则函数将立即返回,并且在设备完成流中的所有工作后,与流关联的资源将自动释放。

八.
默认流

内核启动和主机<->未指定任何流参数或等效于将流参数设置为零的设备内存副本将被发送到默认流。因此,它们是按顺序执行的。

对于使用--default stream per thread编译标志编译的代码(或在包含CUDA头(CUDA.h和CUDA_runtime.h)之前定义CUDA
API_per_thread_default_stream宏的代码),默认流是常规流,每个主机线程都有自己的默认流。

注意:#define CUDA_API_PER_THREAD_DEFAULT_STREAM 1不能用于在nvcc编译代码时启用此行为,因为nvcc在转换单元的顶部隐式包含CUDA_runtime.h。在这种情况下,需要使用--default
stream per thread编译标志,或者需要使用

-DCUDA_API_per_thread_default_stream=1编译器标志定义

CUDA_API_per_thread_default_stream宏。

对于使用--default stream legacy compilation标志编译的代码,默认流是一个称为空流的特殊流,每个设备都有一个用于所有主机线程的空流。空流是特殊的,因为它导致隐式同步,如隐式同步中所述。

对于未指定--default stream compilation标志而编译的代码,假定-default
stream legacy为默认值。

九.显式同步

有多种方法可以显式地同步流。

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

cudaStreamSynchronize()接受流作为参数,并等待给定流中所有前面的命令完成。它可用于将主机与特定流同步,从而允许其他流在设备上继续执行。              cudaStreamWaitEvent()接受一个流和一个事件作为参数(有关事件的描述,请参见事件),并使调用cudaStreamWaitEvent()后添加到给定流的所有命令延迟执行,直到给定事件完成。

cudaStreamQuery()为应用程序提供了一种方法,可以知道流中所有前面的命令是否都已完成。

十.隐式同步

如果主机线程在不同流之间发出以下任一操作,则来自不同流的两个命令不能同时运行:

页锁定的主机内存分配,

设备内存分配,

一个设备内存设置,

两个地址之间到同一设备存储器的存储器副本,

任何对空流的CUDA命令,

在计算能力3.x和计算能力7.x中描述的L1/共享内存配置之间的切换。

对于支持并发内核执行且具有计算能力3.0或更低版本的设备,任何需要依赖性检查以查看流式内核启动是否完成的操作:

只有当从CUDA上下文中的任何流启动的所有先前内核的所有线程块都已开始执行时,才能开始执行;

阻止以后从CUDA上下文中的任何流启动所有内核,直到选中的内核启动完成。

需要依赖项检查的操作包括与要检查的启动和对该流上cudaStreamQuery()的任何调用相同的流中的任何其他命令。因此,应用程序应遵循以下准则,以提高并发内核执行的潜力:              所有独立操作应在从属操作之前发布,任何类型的同步都应尽量延迟。

十一.重叠行为

两个流之间的执行重叠量取决于向每个流发出命令的顺序,以及设备是否支持数据传输和内核执行重叠(请参阅数据传输和内核执行重叠)、并发内核执行重叠(请参阅并发内核执行),和/或并发数据传输(参见并发数据传输)。

例如,在不支持并发数据传输的设备上,创建和销毁代码样本的两个流根本不重叠,因为从主机到设备的内存副本在从设备到主机的内存副本颁发给流[0]之后,会将从主机到设备的内存副本颁发给流[1],因此,它只能在从设备到主机的内存复制完成后启动。如果代码按以下方式重写(并且假设设备支持数据传输和内核执行的重叠)

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]);

然后,向流[1]发出的从主机到设备的内存复制与向流[0]发出的内核启动重叠。

在支持并发数据传输的设备上,创建和销毁的代码示例的两个流确实重叠:从主机到设备的内存副本发出到流[1]与从设备到主机的内存副本发出到流[0]重叠,甚至与内核启动发出到流[0]重叠(假设设备支持数据传输和内核执行重叠)。但是,对于计算能力为3.0或更低的设备,内核执行不可能重叠,因为在将设备到主机的内存副本颁发给流[0]之后,第二次内核启动将颁发给流[1],因此它将被阻止,直到根据隐式同步完成颁发给流[0]的第一次内核启动。如果如上所述重写代码,则内核执行重叠(假设设备支持并发内核执行),因为在将设备到主机的内存副本颁发给流[0]之前,第二次内核启动被颁发给流[1]。然而,在这种情况下,根据隐式同步,从设备到主机的内存拷贝(发给流[0])只与发给流[1]的内核启动的最后一个线程块重叠,后者只能表示内核总执行时间的一小部分。

十二.主机函数(回调)

运行时提供了一种通过cudaLaunchHostFunc()将CPU函数调用插入流中的方法。一旦在回调完成之前向流发出所有命令,则在主机上执行提供的函数。

下面的代码示例在将主机到设备的内存副本、内核启动和设备到主机的内存副本发送到每个流之后,将主机函数MyCallback添加到两个流中的每个流中。此函数将在每个设备到主机的内存复制完成后开始在主机上执行。

void
CUDART_CB MyCallback(cudaStream_t stream, cudaError_t status, 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);

}

在宿主函数之后在流中发出的命令在函数完成之前不会开始执行。

列队到流中的主机函数不能(直接或间接)进行CUDAAPI调用,因为如果它进行这样的调用而导致死锁,它可能会自己等待。

十三.流优先级

流的相对优先级可以在创建时使用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);

CUDA运行时 Runtime(三)的更多相关文章

  1. CUDA运行时 Runtime(一)

    CUDA运行时 Runtime(一)             一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cuda ...

  2. CUDA运行时 Runtime(四)

    CUDA运行时 Runtime(四) 一.     图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将 ...

  3. CUDA运行时 Runtime(二)

    CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...

  4. iOS运行时Runtime浅析

    运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...

  5. Deep Learning部署TVM Golang运行时Runtime

    Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...

  6. “ compiler-rt”运行时runtime库

    " compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...

  7. 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)

    声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...

  8. iOS 运行时runtime控制私有变量以及私有方法

    OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...

  9. 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)

    OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...

随机推荐

  1. 【JVM】JVM中的垃圾回收算法

    1.标记 -清除算法 "标记-清除"(Mark-Sweep)算法,如它的名字一样,算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收 ...

  2. 【原创】Centos8安装ansible

    1.安装步骤 # 安装epel扩展源 dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rp ...

  3. picpick截屏软件脱壳

    0x01 准备 OD 基本查壳软件 picpick可执行文件(不是快捷方式) 0x02 查壳 软件是2018年9月,还是比较新的 显示EP区段是.vmp1,没见过,不知道是压缩壳还是加密壳,搜索所示y ...

  4. ColyseusJS 轻量级多人游戏服务器开发框架 - 中文手册(系统保障篇)

    快速上手多人游戏服务器开发.后续会基于 Google Agones,更新相关 K8S 运维.大规模快速扩展专用游戏服务器的文章.拥抱️原生 Cloud-Native! 系列 ColyseusJS 轻量 ...

  5. restful 与 webapi 详解

    restful 什么是API API全称Aplication Programming Itererface即应用程序编程接口, 我们在开发应用程序时经常用到.API作为接口,用来"连接&qu ...

  6. OO第1.2次作业·魔鬼的三角函数化简

    多年以后,面对办公室的屏幕,我会回忆起开始肝第二周OO作业的那个遥远的下午.那时的程序是一个一两百行的符号求导,基类与接口在包里一字排开,工整的注释一望到底 谁能想到,接下来的十几个小时我要经历什么样 ...

  7. Django(24)永久重定向和临时重定向

    重定向 重定向分为永久重定向和临时重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面.比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面. ...

  8. 在C++中调用Python

    技术背景 虽然现在Python编程语言十分的火爆,但是实际上非要用一门语言去完成所有的任务,并不是说不可以,而是不合适.在一些特定的.对于性能要求比较高的场景,还是需要用到传统的C++来进行编程的.但 ...

  9. Scanner, BufferedReader, InputStreamReader 与ACM模式输入

    Scanner, BufferedReader, InputStreamReader 与ACM模式输入html { -webkit-print-color-adjust: exact } * { bo ...

  10. 马哥Linux SysAdmin学习笔记(一)

    Linux入门 Linux系统管理: 磁盘管理,文件系统管理 RAID基础原理,LVM2 网络管理:TCP/IP协议,Linux网络属性配置 程序包管理:rpm,yum 进程管理:htop,glanc ...