3. 【NVIDIA-GPU-CUDA】Stream的并发

1,074 阅读3分钟

1. 默认流

ref: GPU 专业提示:CUDA 7 流简化了并发性 - NVIDIA 开发者博客

当程序对性能要求不高时,可使用默认流。

1.1. cuda7之前的默认流行为

每个设备具有一个默认流,不同主机线程调用默认流时会产生隐式同步。默认流只有在所有常规流运行完成后方可运行。默认流与常规流存在同步。

1.2. cuda7的默认流行为

每个主机线程具有一个默认流。默认流被视为常规流,不会与其他流发生同步。

1.2.1. 如何开启每线程默认流

要在 nvcc 7 及更高版本中启用每线程默认流,您可以在包含 CUDA 头( cuda.h 或 cuda_runtime.h )之前,使用 nvcc 命令行选项 CUDA 或 #define 编译 CUDA_API_PER_THREAD_DEFAULT_STREAM 预处理器宏。需要注意的是:当代码由 nvcc 编译时,不能使用 #define CUDA_API_PER_THREAD_DEFAULT_STREAM 在. cu 文件中启用此行为,因为 nvcc 在翻译单元的顶部隐式包含了 cuda_runtime.h 。

下图为开普勒GPU上,添加 --default-stream per-thread 选项到nvcc前后的nvvp运行状态图。未添加的情况下,default流导致常规流阻塞。

const int N = 1 << 20;
 
__global__ void kernel(float *x, int n)
{
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    for (int i = tid; i < n; i += blockDim.x * gridDim.x) {
        x[i] = sqrt(pow(3.14159,i));
    }
}
 
int main()
{
    const int num_streams = 8;
 
    cudaStream_t streams[num_streams];
    float *data[num_streams];
 
    for (int i = 0; i < num_streams; i++) {
        cudaStreamCreate(&streams[i]);
 
        cudaMalloc(&data[i], N * sizeof(float));
 
        // launch one worker kernel per stream
        kernel<<<1, 64, 0, streams[i]>>>(data[i], N);
 
        // launch a dummy kernel on the default stream
        kernel<<<1, 1>>>(0, 0);
    }
 
    cudaDeviceReset();
 
    return 0;
}

1.3. 默认流的开发提示

  • 记住:对于每线程的默认流,每个线程中的默认流的行为与常规流相同,只要同步和并发就可以了。对于传统的默认流,这是不正确的。
  • --default-stream 选项是按编译单元应用的,因此请确保将其应用于所有需要它的 nvcc 命令行。
  • cudaDeviceSynchronize() 继续同步设备上的所有内容,甚至使用新的每线程默认流选项。如果您只想同步单个流,请使用 cudaStreamSynchronize(cudaStream_t stream) ,如我们的第二个示例所示。
  • 从 CUDA 7 开始,您还可以使用句柄 cudaStreamPerThread 显式地访问每线程的默认流,也可以使用句柄 cudaStreamLegacy 访问旧的默认流。请注意, cudaStreamLegacy 仍然隐式地与每个线程的默认流同步,如果您碰巧在一个程序中混合使用它们。
  • 您可以通过将 cudaStreamCreate() 标志传递给 cudaStreamCreate() 来创建不与传统默认流同步的 非阻塞流 。 流的异步

2. 流的异步

2.1. 同步流的方式

阻塞CPU线程直到GPU完成工作。多数情况下严重损害主机线程性能。

2.1.1. 暴力流同步

cudaDeviceSynchronize();

2.1.2. 其他流同步

cudaStreamWaitEvent(event);
cudaStreamSynchronize(stream);
cudaStreamQuery(stream);
cudaEventSynchronize(event);
cudaEventQuery(event);

2.2. 异步流拷贝内存

不会增加访存效率