在异步cuda流执行期间摆脱忙碌的等待

时间:2011-02-24 16:12:16

标签: cuda cuda-streams busy-loop

我正在寻找一种方法如何摆脱主机线程中的繁忙等待(不要复制该代码,它只显示我的问题的想法,它有许多基本的错误):

cudaStream_t steams[S_N];
for (int i = 0; i < S_N; i++) {
    cudaStreamCreate(streams[i]);
}
int sid = 0;
for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
     while (true) {
         if (cudaStreamQuery(streams[sid])) == cudaSuccess) { //BUSY WAITING !!!!
             cudaMemcpyAssync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, streams[sid]);
             kernel<<<gridDim, blockDim, smSize streams[sid]>>>(d_data, DATA_STEP);
             break;
         }
         sid = ++sid % S_N;
     }

}

有没有办法让主机线程空闲并等待一些流完成,然后准备并运行另一个流?

编辑:我在代码中添加了(true),以强调忙碌的等待。现在我执行所有流,并检查它们中的哪一个已完成运行另一个新流。 cudaStreamSynchronize等待特定的流完成,但我想等待作为第一个完成工作的任何流。

EDIT2:我摆脱了忙碌的等待:

cudaStream_t steams[S_N];
for (int i = 0; i < S_N; i++) {
    cudaStreamCreate(streams[i]);
}
int sid = 0;
for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
    cudaMemcpyAssync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, streams[sid]);
    kernel<<<gridDim, blockDim, smSize streams[sid]>>>(d_data, DATA_STEP);
    sid = ++sid % S_N;
}
for (int i = 0; i < S_N; i++) {
    cudaStreamSynchronize(streams[i]);
    cudaStreamDestroy(streams[i]);
}

但它看起来比主机线程忙等待的版本慢一点。我认为这是因为,现在我静态地在流上分配作业,所以当一个流完成工作时,它是空闲的,直到每个流完成工作。以前的版本动态地将工作分配给第一个空闲流,因此效率更高,但是在主机线程上忙着等待。

5 个答案:

答案 0 :(得分:4)

真正的答案是使用 cudaThreadSynchronize 等待所有以前的启动完成, cudaStreamSynchronize 等待某个流中的所有启动完成, cudaEventSynchronize 等待只记录特定流上的某个事件。

但是,您需要了解流和同步的工作方式,然后才能在代码中使用它们。


如果您根本不使用流,会发生什么?请考虑以下代码:

kernel <<< gridDim, blockDim >>> (d_data, DATA_STEP);
host_func1();
cudaThreadSynchronize();
host_func2();

启动内核,主机继续执行host_func1和kernel。然后,主机和设备同步,即主机等待内核完成,然后再转到host_func2()。

现在,如果你有两个不同的内核怎么办?

kernel1 <<<gridDim, blockDim >>> (d_data + d1, DATA_STEP);
kernel2 <<<gridDim, blockDim >>> (d_data + d2, DATA_STEP);

kernel1是异步启动的!主机继续运行,kernel2在kernel1完成之前启动!但是,在 kernel1完成之后,kernel2将不会执行,因为它们都已在流0(默认流)上启动。考虑以下替代方案:

kernel1 <<<gridDim, blockDim>>> (d_data + d1, DATA_STEP);
cudaThreadSynchronize();
kernel2 <<<gridDim, blockDim>>> (d_data + d2, DATA_STEP);

完全没有必要这样做,因为设备已经同步在同一个流上启动的内核。

所以,我认为您正在寻找的功能已经存在...因为内核始终等待先前在同一个流中启动以在启动之前完成(即使主机经过)。也就是说,如果您想等待任何上一次启动完成,那么只需使用流。这段代码可以正常工作:

for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
    cudaMemcpyAsync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, 0);
    kernel<<<gridDim, blockDim, smSize, 0>>>(d_data, DATA_STEP);
 }

现在,流到溪流。您可以使用流来管理并发设备执行。

将流视为队列。您可以将不同的memcpy调用和内核启动放入不同的队列中。然后,流1中的内核和流2中的启动是异步的!它们可以同时或以任何顺序执行。如果您想确保设备上一次只执行一个memcpy / kernel,那么使用流。同样,如果您希望按特定顺序执行内核,则使用流。

那就是说,请记住,放入流1的任何内容都是按顺序执行的,所以不要打扰同步。同步用于同步主机和设备调用,而不是两个不同的设备调用。因此,如果您想同时执行多个内核,因为它们使用不同的设备内存并且彼此没有影响,那么请使用流。有点像...

cudaStream_t steams[S_N];
for (int i = 0; i < S_N; i++) {
    cudaStreamCreate(streams[i]);
}

int sid = 0;
for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
     cudaMemcpyAsync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, streams[sid]);
     kernel<<<gridDim, blockDim, smSize streams[sid]>>>(d_data, DATA_STEP);
     sid = ++sid % S_N;
 }

无需显式设备同步。

答案 1 :(得分:3)

有:cudaEventRecord(event, stream)cudaEventSynchronize(event)。参考手册http://developer.download.nvidia.com/compute/cuda/3_2/toolkit/docs/CUDA_Toolkit_Reference_Manual.pdf包含所有详细信息。

编辑:BTW流对于内核和内存传输的并发执行非常方便。为什么要等待当前流完成序列化执行?

答案 2 :(得分:3)

我解决这个问题的想法是每个流有一个主机线程。该主机线程将调用cudaStreamSynchronize以等待流命令完成。 不幸的是,在CUDA 3.2中不可能,因为它只允许一个主机线程处理一个CUDA上下文,这意味着每个支持CUDA的GPU有一个主机线程。

希望在CUDA 4.0中可以:CUDA 4.0 RC news

编辑:我在CUDA 4.0 RC中测试过,使用open mp。我为每个cuda流创建了一个主机线程。它开始起作用了。

答案 3 :(得分:1)

而不是cudaStreamQuery,您需要cudaStreamSynchronize

int sid = 0;
for (int d = 0; d < DATA_SIZE; d+=DATA_STEP) {
     cudaStreamSynchronize(streams[sid]);
     cudaMemcpyAssync(d_data, h_data + d, DATA_STEP, cudaMemcpyHostToDevice, streams[sid]);
     kernel<<<gridDim, blockDim, smSize streams[sid]>>>(d_data, DATA_STEP);
     sid = ++sid % S_N;
}

(您还可以使用cudaThreadSynchronize等待所有流的启动,以及使用cudaEventSynchronize的事件,以实现更高级的主机/设备同步。)

您可以进一步控制这些同步功能发生的等待类型。查看cudaDeviceBlockingSync标志和其他人的参考手册。不过,默认值可能就是你想要的。

答案 4 :(得分:1)

您需要复制数据块并在不同 for循环的数据块上执行内核。那会更有效率。

像这样:

size = N*sizeof(float)/nStreams;

for (i=0; i<nStreams; i++){
offset = i*N/nStreams;
cudaMemcpyAsync(a_d+offset, a_h+offset, size, cudaMemcpyHostToDevice, stream[i]);
}


for (i=0; i<nStreams; i++){
offset = i*N/nStreams;
kernel<<<N(nThreads*nStreams), nThreads, 0, stream[i]>>> (a_d+offset);
}

通过这种方式,内存副本不必等待前一个流的内核执行,反之亦然。