CUDA C/C++ 流和并发

一、并发

1、同时执行多个CUDA操作的能力(超越多线程并行)

CUDA Kernel <<<>>>

cudaMemcpyAsync(HostToDevice)

cudaMemcpyAsync(DeviceToHost)

CPU上的操作

2、Fermi 体系结构可以同时支持(计算能力2.0+)

GPU上最多16个CUDA内核

2个cudaMemcpyAsyncs(必须在不同方向)

CPU上的计算

二、流

1、流

在GPU上按发布顺序执行的一系列操作

2、用于影响并发的编程模型

不同流中的CUDA操作可以同时运行

来自不同流的CUDA操作可以交织

三、并发示例

CUDA C/C++ 流和并发_第1张图片

1、串行:将数据从CPU传输到GPU,GPU核函数执行计算操作,将计算结果从GPU传输到CPU上。

2、并行:重叠内核和设备到主机的内存拷贝,即内核执行的同时,可以将GPU上的数据拷贝到CPU上。

四、并发量

CUDA C/C++ 流和并发_第2张图片

1、串行:1倍的性能

2、2路并发:最多2倍的性能提升

3、3路并发:最多3倍的性能提升

4、4路并发:最多3倍多性能提升

5、多路并发

五、举例 - Tiled DGEMM

1、CPU(4 core Westmere x5670 @2.93GHZ,MKL)

           43 Gflops

2、GPU(C2070)

           串行:125 Gflops(2.9x)

           2-路并行:177 Gflops(4.1x)

           3-路并行:262 Gflops(6.1x)

3、GPU + CPU

          4-路并行:282 Gflops (6.6x)

          对于更高级的显卡,最大可以达到 330 Gflops

4、通过利用并发性获得最大性能

5、所有通信隐藏 - 有效地消除设备内存大小限制

CUDA C/C++ 流和并发_第3张图片

六、默认的流(流 ‘0’ )

1、未指定流时使用默认的流。

2、完全同步的w.r.t.主机和设备:好像在每次CUDA操作前后都插入了cudaDeviceSynchronize()

3、异常:异步w.r.t.主机

(1)内核在默认流中启动

(2)cudaMemcpy*Async

(3)cudaMemset*Async

(4)同一设备中使用cudaMemcpy

(5)小于或等于64KB的H2D(主机到设备) cudaMemcpy

七、并发性要求

1、CUDA操作必须位于不同的非0流中

2、来自“固定”内存的主机与cudaMemcpyAsync

    分页锁定的内存

    使用cudaMallocHost()或cudaHostAlloc()分配内存

3、必须有足够的资源

    在不同的方向使用cudaMemcpyAsyncs

    设备资源(SMEM,registers,blocks,etc.)

八、简单的例子:同步

完全的同步操作

cudaMalloc(&dev1,size);						   // 给GPU分配空间
double* host1 = (double*)malloc(&host1,size);  // 给CPU分配空间

...

cudaMemcpy(dev1,host1,size,H2D);  // 从CPU上拷数据到GPU
kernel2 <<< grid,block,0 >>>(...,dev2,...);  // GPU上做计算操作
kernel3 <<< grid,block,0 >>>(...,dev3,...);  // GPU上做计算操作
cudaMemcpy(host4,dev4,size,D2H);			 // 将计算结果从GPU上拷贝到CPU上

...

默认流中的所有CUDA操作都是同步的。

九、简单的例子:异步,不使用流

默认情况下,GPU内核与主机异步。

cudaMalloc(&dev1,size);						   // 给GPU分配空间
double* host1 = (double*)malloc(&host1,size);  // 给CPU分配空间

...

cudaMemcpy(dev1,host1,size,H2D);  // 从CPU上拷数据到GPU
kernel2 <<< grid,block,0 >>>(...,dev2,...);  // GPU上做计算操作
some_CPU_method();  // 这一行与上一行存在潜在的并行
kernel3 <<< grid,block,0 >>>(...,dev3,...);  // GPU上做计算操作
cudaMemcpy(host4,dev4,size,D2H);			 // 将计算结果从GPU上拷贝到CPU上

...

十、简单的例子:异步,使用流

完全异步/并发

并发操作使用的数据应该是独立的

cudaStream_t stream1,stream2,stream3,stream4;
cudaStreamCreate(&stream1);
...

cudaMalloc(&dev1,size);						   // 给GPU分配空间
cudaMallocHost(&host1,size);
...

cudaMemcpyAsunc(dev1,host1,size,H2D,stream1);  // 从CPU上拷数据到GPU
kernel2 <<< grid,block,0,stream2 >>>(...,dev2,...);  // GPU上做计算操作
kernel3 <<< grid,block,0,stream3 >>>(...,dev3,...);  // GPU上做计算操作
cudaMemcpyAsync(host4,dev4,size,D2H,stream4);			 // 将计算结果从GPU上拷贝到CPU上
some_CPU_method();
...

十一、显式同步

1、同步一切

    cudaDeviceSunchronize()

    阻止主机,直到所有发出的CUDA调用完成。

2、同步w.r.t. 特定的流

    cudaStreamSynchronize(streamid)

    阻止主机,直到streamid中的所有CUDA调用完成

3、使用事件同步

    在流中创建特定的“事件”以用于同步

    cudaEventRecord(event,streamid)

    cudaEventSynchronize(event)

    cudaStreamWiatEvent(stream,event)

    cudaEventQuery(event)

4、显示同步示例

使用事件解决

{
	cudaEvent_t event;
	cudaEventCreate(&event);
	
	cudaMemcpyAsync(d_in,in,size,H2D,stream1);
	cudaEventRecord(event,stream1);
	
	cudaMemcpyAsync(out,d_out,size,D2H,stream2);
	
	cudaStreamWaitEvent(stream2,event);
	kernel<<<,,,stream2>>>(d_in,d_out);
	
	asynchronousCPUmethod(...);
}

十二、隐式同步

1、这些操作隐式同步所有其他CUDA操作

(1)分页锁定的内存分配 

        cudaMallocHost 

        cudaHostAlloc

(2)设备内存分配

        cudaMalloc

(3)非异步版本的内存操作

        cudaMemcpy* (no Async suffix) 

        cudaMemset* (no Async suffix)

(4)更改为L1 /shared 内存配置

        cudaDeviceSetCacheConfig

十三、流调度

1、Fermi硬件有三个队列

(1)1个计算引擎队列

(2)2个复制引擎队列–一个用于H2D,一个用于D2H

2、CUDA操作按其发出的顺序分派给硬件

(1)放在相关队列中

(2)引擎队列之间的流依赖关系得到维护,但在引擎队列中丢失

3、在以下情况下,将从引擎队列中调度CUDA操作:

(1)同一流中的先前调用已完成

(2)已调度同一队列中的先前调用,并且

(3)资源可用

4、如果CUDA内核位于不同的流中,则可以同时执行

如果前面内核的所有线程块都已调度,并且仍有可用的SM资源,则会调度给定内核的线程块

5、请注意,被阻止的操作会阻止队列中的所有其他操作,即使在其他流中也是如此

十四、示例–阻塞队列

1、两个流,流1首先发布

(1)Stream 1::HDa1,HDb1,K1,DH1(首先发出)

(2)Stream 2:DH2(完全独立于流1)

CUDA C/C++ 流和并发_第4张图片

2、两个流,流2首先发布issue order matters

(1)Stream 1:HDa1,HDb1,K1,DH1

(2)Stream 2:DH2(issued first)

CUDA C/C++ 流和并发_第5张图片

十五、示例-阻塞内核 - issue order matters

1、两个流–仅发布CUDA内核

(1)Stream 1:Ka1,Kb1

(2)Stream 2:Ka2,Kb2

(3)内核大小一样,占SM资源的½

2、发行深度优先

CUDA C/C++ 流和并发_第6张图片

3、发行广度优先

CUDA C/C++ 流和并发_第7张图片

十六、示例-最佳并发性取决于内核执行时间

1、两个流-仅发布CUDA内核-但是内核的“大小”不同

issue order matters! execution time matters!

(1)Stream 1 : Ka1 {2}, Kb1 {1}

(2)Stream 2 : Kc2 {1}, Kd2 {2}

(3)内核占SM资源的一半

2、深度优先

CUDA C/C++ 流和并发_第8张图片

3、广度优先

CUDA C/C++ 流和并发_第9张图片

4、自定义

CUDA C/C++ 流和并发_第10张图片

十七、并发内核调度

1、并发内核调度是一种特殊的调度方法

2、通常,在操作之后,将信号插入队列,以在同一流中启动下一个操作

3、对于计算引擎队列,要启用并发内核,当按顺序发布计算内核时,该信号将延迟到最后一个按顺序计算内核之后

4、在某些情况下,这种信号延迟会阻塞其他队列

十八、示例–并发内核和阻塞

1、三个流,分别为(HD,K,DH)

2、广度优先,顺序发出的内核延迟信号并阻塞cudaMemcpy(D2H)

CUDA C/C++ 流和并发_第11张图片

3、深度优先,“通常”最适合Fermi体系架构,即执行GPU的过程中,同时可以将GPU中计算的结果传输到CPU上

CUDA C/C++ 流和并发_第12张图片

十九、以前的架构

1、计算能力1.0+

支持GPU / CPU并发

2、计算能力1.1+(即C1060)

增加了对异步内存复制的支持(单引擎),(某些异常–使用asyncEngineCount设备属性检查)

3、计算能力2.0+(即C2050)

添加对并发GPU内核的支持,(某些异常–使用concurrentKernels设备属性检查)

添加第二个复制引擎以支持双向存储,(某些异常–使用asyncEngineCount设备属性检查

二十、额外细节

1、很难同时运行四个以上的内核

2、可以使用环境变量禁用并发:CUDA_LAUNCH_BLOCKING

3、cudaStreamQuery可用于分离顺序内核并防止延迟信号

4、使用8个以上纹理的内核不能同时运行

5、切换 L1 / Shared 配置将破坏并发性

6、要同时运行,CUDA操作必须具有不超过62个中间CUDA操作,也就是说,按照“发出顺序”,它们之间不能相隔超过62个其他问题;进一步的操作被序列化。

7、cudaEvent_t is useful for timing, but for performance use
cudaEventCreateWithFlags ( &event, cudaEventDisableTiming )

二十一、并发准则

1、代码到编程模型–流

未来的设备将不断改善流模型的硬件表示

2、注意发布顺序

可能会有所不同

3、注意可能破坏并发性的资源和操作

(1)默认流中的所有内容

(2)事件和同步

(3)流查询

(4)L1 /Shared 配置更改

(5)8种以上的纹理

4、使用工具(Visual Profiler,Parallel Ensight)可视化并发,但这些当前不显示并发内核。

二十二、问题

1、验证Stream-0中的cudaMemcpyAsync()后跟Kernel <<< >>>,该memcpy将阻止内核,但都不会阻止主机。

2、下列操作(或类似操作)是否对 64 'out-ofissue-order' 限制有所贡献?

    cudaStreamQuery

    cudaWaitEvent

3、我知道'query'操作cudaStreamQuery()可以放在引擎或复制队列中,任何查询实际进入的队列都很难确定,并且这可能导致某些阻塞。

你可能感兴趣的:(cuda,CUDA,流和并发)