CUDA编程之Stream

‌1、基本概念
  • 定义‌:CUDA流(Stream)是一组在GPU上按顺序执行的操作序列,不同流中的操作可并行或交错执行,以提升计算效率‌。

  • 作用‌:通过异步操作和并发执行,实现数据传输与核函数执行的重叠,减少整体执行时间‌。

  • 条件‌:使用CUDA流,首先要选择一个支持设备重叠(Device Overlap)功能的设备,支持设备重叠功能的GPU能够在执行一个CUDA核函数的同时,还能在主机和设备之间执行复制数据操作。

2、核心操作
  • 创建与销毁‌:
    使用 cudaStreamCreate() 创建流,cudaStreamDestroy() 销毁流。支持创建多个流以实现并发‌。

    cudaStream_t stream‌[2];
    for (int i=0; i<2; ++i) 
        cudaStreamCreate(&stream[i]);
    // 销毁
    for (int i=0; i<2; ++i)
        cudaStreamDestroy(stream[i]);
    
3、同步机制

  • 流内同步‌:cudaStreamSynchronize(stream) 等待指定流内所有操作完成‌。
  • 全局同步‌:cudaDeviceSynchronize() 等待设备上所有流完成‌。

4. ‌性能优化

  • 数据传输与计算重叠‌:
    使用 cudaMemcpyAsync() 异步传输数据,结合不同流中的核函数执行,隐藏内存复制延迟‌。
    // 示例:异步数据传输与核函数并发执行 
    cudaMemcpyAsync(devPtr, hostPtr, size, cudaMemcpyHostToDevice, stream); 
    kernel<<<grid, block, 0, stream>>>(devPtr);
  • 多流并发‌:
    将独立任务分配到不同流中,利用GPU多任务处理能力提升吞吐量‌。
  • 事件同步‌:通过 cudaEventRecord() 和 cudaStreamWaitEvent() 实现流间依赖控制‌。
    cudaError_t cudaStreamWaitEvent(cudaStream_t stream, cudaEvent_t event, unsigned int flags = 0);
  • stream‌:需等待事件的目标流。
  • event‌:被等待的事件,需已通过 cudaEventRecord() 记录到另一个流中。
  • flags‌:保留参数,通常设为 0
  •  cudaStreamWaitEvent强制指定一个流(stream)等待某个事件(event)完成后再继续执行后续任务,从而实现流间同步‌。
  • 示例:
    流 A 的核函数需等待流 B 的数据传输完成后才能执行。
    cudaEvent_t event;
    cudaEventCreate(&event);
    // 流 B 记录事件
    cudaMemcpyAsync(..., stream_B);
    cudaEventRecord(event, stream_B);
    // 流 A 等待事件
    cudaStreamWaitEvent(stream_A, event);
    kernel_A<<<..., stream_A>>>();
    此操作确保 kernel_A 仅在流 B 的数据传输完成后启动‌ 。
5. 代码实例
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>

__global__ void vector_add(float *a, float *b, float *c, int size){
	int tid = blockIdx.x*blockDim.x + threadIdx.x;
	if(tid < size){
		c[tid] = a[tid] + b[tid];
	}
}

int main() {

	//获取设备属性  
	cudaDeviceProp prop;
	int deviceID;
	cudaGetDevice(&deviceID);
	cudaGetDeviceProperties(&prop, deviceID);
 
	//检查设备是否支持重叠功能  
	if (!prop.deviceOverlap)
	{
		printf("No device will handle overlaps. so no speed up from stream.\n");
		return 0;
	}

	//创建一个CUDA流
	cudaStream_t stream;
	cudaStreamCreate(&stream);

	int size = 1000;
	float *a, *b, *c, *c_host;
	c_host = (float*)malloc(sizeof(float)*size);
	//设备内存分配
	cudaMalloc(&a, sizeof(float)*size);
	cudaMalloc(&b, sizeof(float)*size);
	cudaMalloc(&c, sizeof(float)*size);

	//初始化及数据传输
	float a_host[1000];
	float b_host[1000];
	int n = 0;
	for(n=0; n<size; n++){
		a_host[n] = 1.0;
		b_host[n] = 99.0;
	}
	cudaMemcpyAsync(a,a_host,sizeof(float)*size,cudaMemcpyHostToDevice, stream);
	cudaMemcpyAsync(b,b_host,sizeof(float)*size,cudaMemcpyHostToDevice, stream);
	//////////////////////////////////

	dim3 block(256); //每Block 256线程
	dim3 grid((size+256-1)/256);//计算所需Block数(这里根据size计算)
	vector_add<<<grid,block,0,stream>>>(a,b,c,size);
	
	//结果回传到主机内存
	cudaMemcpyAsync(c_host,c,sizeof(float)*size,cudaMemcpyDeviceToHost, stream);

  	//等待指定流内所有操作完成‌
	cudaStreamSynchronize(stream);

	printf("host data head:%.2f tail:%.2f\n", c_host[0], c_host[size-1]);

	cudaFree(a);
	cudaFree(b);
	cudaFree(c);
	free(c_host);

	cudaStreamDestroy(stream);

    return 0;
}

    ### CUDA 流的概念 CUDA 流(Stream)是一种用于管理 GPU 上操作顺序和并发性的机制。通过使用多个流,开发者可以在不同流之间实现任务的重叠执行,从而提高硬件利用率和程序性能。 #### 基本概念 - **默认流**:每个 CUDA 上下文中都有一个默认流(Default Stream),它负责串行化所有提交给它的操作。如果未显式指定流,则这些操作会进入默认流[^1]。 - **异步行为**:除了默认流外,其他流都具有异步特性,这意味着当某个流中的操作被发起后,CPU 可以立即继续处理后续的任务而无需等待该操作完成。 #### 创建与销毁流 可以通过 `cudaStreamCreate` 函数创建一个新的流实例,并用 `cudaStreamDestroy` 销毁不再使用的流资源: ```c++ cudaStream_t stream; cudaStreamCreate(&stream); // 使用流... cudaStreamDestroy(stream); ``` #### 数据传输与核函数调用 在实际应用中,通常需要将主机内存的数据复制到设备端或者反过来;同时也可能涉及多次调用同一个或不同的 kernel 来完成复杂的计算逻辑。借助于自定义流,我们可以让上述过程更加高效地并行起来。例如下面的例子展示了如何利用两个独立的 streams 同时上传数据至GPU 并运行各自的 kernels: ```cpp float *d_A, *d_B; // Device pointers float *h_A, *h_B; // Host buffers filled with data... cudaMalloc((void**)&d_A, size); cudaMalloc((void**)&d_B, size); // Create two separate streams. cudaStream_t s1,s2; cudaStreamCreate(&s1); cudaStreamCreate(&s2); // Asynchronously copy A to device on first stream cudaMemcpyAsync(d_A,h_A,size,cudaMemcpyHostToDevice,s1); myKernel<<<blocksPerGridA,threadsPerBlockA,0,s1>>>(...parameters...) // Similarly do async operations related B but now using second stream 's2' cudaMemcpyAsync(d_B,h_B,size,cudaMemcpyHostToDevice,s2); anotherKernel<<<blocksPerGridB,threadsPerBlockB,0,s2>>>(...params...) // Cleanup resources after all work done ... cudaFree(d_A); cudaFree(d_B); cudaStreamDestroy(s1); cudaStreamDestroy(s2); ``` 这里需要注意的是,尽管来自不同streams 的指令可能会交错被执行,但是属于同一stream内的各项命令仍然保持其相对次序不变——即先发出的操作总是会在之后启动的那个之前结束。 #### 同步控制 为了协调多条流之间的相互作用关系,有时还需要引入额外的同步手段。这可通过事件(event)对象来达成目的。简单来说就是记录特定时刻的状态以便稍后再查询确认是否达到预期进展程度。 ```c++ cudaEvent_t event; cudaEventCreate(&event); someOperation<<<numBlocks,numThreads,sharedMemSize,customStream>>>(); cudaEventRecord(event, customStream); // Do other stuff here possibly involving different streams etc. // Later somewhere else where synchronization needed before proceeding further.. cudaEventSynchronize(event); cudaEventDestroy(event); ``` 以上介绍了关于CUDA Streams的一些基础知识及其典型应用场景下的编码实践方法论等内容[^2]。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    byxdaz

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值