CUDA编程接口:异步并发执行的概念和API

  1.主机和设备间异步执行

  为了易于使用主机和设备间的异步执行,一些函数是异步的:在设备完全完成任务前,控制已经返回给主机线程了。它们是: 内核发射; 设备间数据拷贝函数; 主机和设备内拷贝小于64KB的存储器块时; 存储器拷贝函数中带有Async后缀的; 设置设备存储器的函数调用。

  程序员可通过将CUDA_LAUNCH_BLOCKING环境变量设置为1来全局禁用所有运行在系统上的应用的异步内核发射。提供这个特性只是为了调试,永远不能作为使软件产品运行得可靠的方式。 当应用通过CUDA调试器或CUDA profiler(cuda-gdb, CUDA Visual Profiler, Parallel Nsight)运行时,所有的内核发射都是同步的。

  2.数据传输和内核执行重叠

  一些计算能力1.1或更高的设备可在内核执行时,在分页锁定存储器和设备存储器之间拷贝数据。应用可以通过检查asyncEngineCount 设备属性查询这种能力,如果其大于0,说明设备支持数据传输和内核执行重叠。这种能力目前只支持不涉及CUDA数组和使用cudaMallocPitch()分配的二维数组的存储器拷贝( 见前文,可阅读“相关阅读”中的文章)。

  3. 并发内核执行

  一些计算能力2.x的设备可并发执行多个内核。应用可以检查concurrentKernels属性以查询这种能力)(后续文章将介绍),如果等于1,说明支持。 设备最大可并发执行的内核数目是16。 来自不同CUDA上下文的内核不能并发执行。 使用了许多纹理或大量本地存储器的内核和其它内核并发执行的可能性比较小。

  4. 并发数据传输

  在计算能力2.x的设备上,从主机分页锁定存储器复制数据到设备存储器和从设备存储器复制数据到主机分页锁定存储器,这两个操作可并发执行。 应用可以通过检查asyncEngineCount 属性查询这种能力,如果等于2,说明支持。

  5. 流

  应用通过流管理并发。流是一系列顺序执行的命令(可能是不同的主机线程发射)。另外,流之间相对无序的或并发的执行它们的命令;这种行为是没有保证的,而且不能作为正确性的的保证(如内核间的通信没有定义)。

  ①创建和销毁

  可以通过创建流对象来定义流,且可指定它作为一系列内核发射和设备主机间存储器拷贝的流参数。下面的代码创建了两个流且在分页锁定存储器中分配了一个名为hostPtr的浮点数组。

cudaStream_t stream[2];

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

cudaStreamCreate(&stream[i]);

float* hostPtr;

cudaMallocHost((void**)&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()等待指定流中所有之前的任务完成,然后释放流并将控制权返回给主机线程。

  ② 默认流

  内核启动和没有使用流参数的主机设备间数据拷贝,或者等价地将流参数设为0,此时发射到默认流。因此顺序执行。

  ③显式同步

  有很多方法显式的在流之间同步。

  cudaDeviceSynchronize()直到前面所有流中的命令都执行完。

  cudaStreamSynchronize()以某个流为参数,强制运行时等待该流中的任务都完成。可用于同步主机和特定流,同时允许其它流继续执行。

  cudaStreamWaitEvent()以一个流和一个事件为参数(后文将介绍),使得在调用cudaStreamWaitEvent()后加入到指定流的所有命令暂缓执行直到事件完成。流可以是0,此时在调用cudaStreamWaitEvent()后加入到所有流的所有命令等待事件完成。

  cudaStreamQuery()用于查询流中的所有之前的命令是否已经完成。

  为了避免不必要的性能损失,这些函数最好用于计时或隔离失败的发射或存储器拷贝。

  ④隐式同步

  如果是下面中的任何一种情况,来自不同流的两个命令也不能并发:分页锁定主机存储器分配,设备存储器分配,设备存储器设置,设备之间存储器拷贝,默认流中调用的任何CUDA命令, F.4.1节描述的一级缓存/共享存储器之间配置切换。

  对于支持并发内核执行的设备,任何需要依赖检测以确定内核发射是否完成的操作:

  1)只有来自CUDA上下文中的任何流中的所有的前面的内核启动的线程块开始执行,才能够开始执行;

  2)会阻塞CUDA上下文中后面任何流中所有的内核发射直至被检测的内核发射完成。

  需要依赖检测的操作包括同一个流中的一些其它类似被检查的发射的命令和流中的任何cudaStreamQuery()调用。因此,应用应当遵守这些指导以提升潜在的内核并发执行:

  1)所有独立操作应当在依赖操作之前发出,

  2)任何类型同步尽量延后。

  ⑤重叠行为

  两个流的重叠执行数量依赖于发射到每个流的命令的顺序和设备是否支持数据传输和内核执行重叠、并发内核执行、并发数据传输。

  例如,在不支持并发数据传输的设备上,前面例程的两个流并没有重叠,因为发射到流1的从主机到设备的存储器拷贝在发射到流0的从设备到主机的存储器拷贝之后,因此只有发射到流0的设备到主机的存储器拷贝完成它才开始。如果代码重写成如下方式(同时假设设备支持数据传输和内核执行重叠)。

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的内核执行(假设设备支持数据传输和内核执行重叠)。但是内核执行不可能重叠,因为发射到流1的第二个内核执行在发射到流0的设备到主机的存储器拷贝之后,因此会被阻塞直到发射到流0的内核执行完成。如果代码被重写成上面的样子,内核执行就重叠了(假设设备支持并发内核执行),因为发射到流1的第二个内核执行在发射到流0的设备到主机的存储器拷贝之前。然而在这种情况下,发射到流0的设备到主机的存储器只和发射到流1的内核执行的最后一个线程块重叠,这只占总内核执行时间的一小部分。

  6.事件

  通过在应用的任意点上异步地记载事件和查询事件是否完成,运行时提供了精密地监测设备运行进度和精确计时。当事件记载点前面,事件指定的流中的所有任务或者指定流中的命令全部完成时,事件被记载。只有记载点之前所有的流中的任务/命令都已完成,0号流的事件才会记载。

  ①创建和销毁

  下面的代码创建了两个事件:

cudaEvent_t start, stop; cudaEventCreate(&start); cudaEventCreate(&stop);

  以下面的方式销毁它们:

cudaEventDestroy(start); cudaEventDestroy(stop);

  ②过去的时间

  节建立的事件可以用下面的方式给3.2.5.5.1节的代码计时:

cudaEventRecord(start, 0); for (int i = 0; i < 2; ++i){ cudaMemcpyAsync(inputDev + i * size, inputHost + i * size, size, cudaMemcpyHostToDevice, stream[i]); MyKernel<<<100, 512, 0, stream[i]>>> (outputDev + i * size, inputDev + i * size, size); cudaMemcpyAsync(outputHost + i * size, outputDev + i * size, size, cudaMemcpyDeviceToHost, stream[i]); cudaEventRecord(stop, 0); cudaEventSynchronize(stop); float elapsedTime; cudaEventElapsedTime(&elapsedTime, start, stop);

  7.同步调用

  直到设备真正完成任务,同步函数调用的控制权才会返回给主机线程。在主机线程执行任何其它CUDA调用前,通过调用cudaSetDeviceFlags()并传入指定标签(参见参考手册)可以指定主机线程的让步,阻塞,或自旋状态。

更多内容请点击:

CUDA专区:http://cuda.it168.com/

CUDA论坛:http://cudabbs.it168.com/

时间: 2024-08-05 11:23:27

CUDA编程接口:异步并发执行的概念和API的相关文章

多接口异步并发调用分析

多接口异步并发调用分析 原创: Keqi 易企秀工程师 3月29日 原文地址:https://www.cnblogs.com/yuanjiangw/p/10848327.html

CUDA编程

目录: 1.什么是CUDA 2.为什么要用到CUDA 3.CUDA环境搭建 4.第一个CUDA程序 5. CUDA编程 5.1. 基本概念 5.2. 线程层次结构 5.3. 存储器层次结构 5.4. 运行时API 5.4.1. 初始化 5.4.2. 设备管理 5.4.3. 存储器管理 5.4.3.1. 共享存储器 5.4.3.2. 常量存储器 5.4.3.3. 线性存储器 5.4.3.4. CUDA数组 5.4.4. 流管理 5.4.5. 事件管理 5.4.6. 纹理参考管理 5.4.6.1.

异步并发利器:实际项目中使用CompletionService提升系统性能的一次实践

场景 随着互联网应用的深入,很多传统行业也都需要接入到互联网.我们公司也是这样,保险核心需要和很多保险中介对接,比如阿里.京东等等.这些公司对于接口服务的性能有些比较高的要求,传统的核心无法满足要求,所以信息技术部领导高瞻远瞩,决定开发互联网接入服务,满足来自性能的需求. 概念 CompletionService将Executor和BlockingQueue的功能融合在一起,将Callable任务提交给CompletionService来执行,然后使用类似于队列操作的take和poll等方法来获

GPU编程接口

一直以来,编写GPU程序主要是通过图形API(OpenGL或DirectX)来完成,但由于图形编程的学习曲线相对较长,运用GPU进行高性能运算由于软件层面的限制难以大面积普及.2007年,由nVidia推出的CUDA(Compute Unified Device Architecture,统一计算设备架构)编程接口弥补了这一不足.利用CUDA编程接口,可以用C语言直接调用GPU资源,而无需将其映射到图形API,为GPU的非图形编程普及消除了障碍.

CUDA C编程入门-编程接口(3.2)CUDA C运行时

在cudart库里实现了CUDA C运行时,应用可以链接静态库cudart.lib或者libcudart.a,动态库cudart.dll或者libcudart.so.动态链接cudart.dll或者libcudart.so的应用需要把CUDA的动态链接库(cudart.dll或者libcudart.so)包含到应用的安装包里. CUDA所有的运行时函数都以cuda为前缀. 在异构编程这一章节提到,CUDA编程模型假设系统是由一个自带各自的内存的主机和设备组成.设备内存这一小节概述用于管理设备内存

CUDA C编程入门-编程接口

CUDA C给熟悉C编程语言的人提供一个简单的途径去编写在设备(GPU)上执行的代码. 由一个最小的C语言的扩展集和运行时库组成. 核心的语言扩展在编程模型这一章节已经介绍过了.允许程序员定义核函数并且使用一些新的语法指定核函数每次运行时的grid和block的维数.可以在C语言扩展这个章节里找到扩展的完整描述.所有的含有这些扩展的源代码都需要使用nvcc编译,nvcc的概述可以查看使用nvcc编译这一小节. 在CUDA C运行这一小节介绍运行时.运行时提供在主机执行的用于分配和回收设备内存.设

Java 异步任务执行服务(一):基本概念和原理

1. 异步任务执行服务是什么意思? 答: 线程 Thread 既表示要执行的任务(run() 方法),又表示执行的机制(start() 方法). Java 并发包提供了一套框架,大大简化了执行异步任务所需的开发,这套框架引入了一个"执行服务"的概念. 执行服务将任务的提交和任务的执行相分离,"执行服务"封装了任务执行的细节,对于任务提交者而言,它可以关注于任务本身,如提交任务.获取结果.取消任务,而不需要关注任务执行的细节,如线程的创建.任务调度.线程关闭等. 任务

并发编程中的几个名词概念

现在,高并发,高流量已成为行业的热点,并且各种高并发的技术也是层出不穷,如论是官方文档还是市面上的各种书籍,我们在阅读的时候都会遇到一些专业名词,理解这些专业名词之后,才能更好的理解内容. 一.同步与异步 介绍: 同步和异步通常来形容一次方法调用. 解释一:同步方法调用一旦开始,调用者必须等到方法的调用返回后,才能继续后续的行为.异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作. 解释二:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信

CUDA C编程入门-编程接口(3.5)模式转换

GPUs有一个显示输出,输出到一个叫主表面的DRAM内存中,这个表面被用于刷新输出给用户看的显示设备.当用户通过改变显示器的分辨率或者位深(使用NVIDIA控制面版或者Windows显示器控制面版),开始一个显示器模式选择时,主表面需要的内存数量会改变.例如,如果以后改变显示器分辨率从1280x1024x32位到1600x1200x32位,系统必须增加7.68MB而不是5.24MB的内存给主表面(带反走样的全屏的图形应用可能要求更多的显示内存给主表面).在Windows上,其它事件,包括加载一个