我正在寻找一种方法如何摆脱主机线程中的繁忙等待(不要复制该代码,它只显示我的问题的想法,它有许多基本的错误):
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]);
}
但它看起来比主机线程忙等待的版本慢一点。我认为这是因为,现在我静态地在流上分配作业,所以当一个流完成工作时,它是空闲的,直到每个流完成工作。以前的版本动态地将工作分配给第一个空闲流,因此效率更高,但是在主机线程上忙着等待。
答案 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);
}
通过这种方式,内存副本不必等待前一个流的内核执行,反之亦然。