我试图让cudaMemcpyHost2Device使用cudaStreamAddCallback等待某些特定事件。我找到了关于cudaStreamCallback API的评论
回调将阻止流中的后续工作,直到它完成。
因此,预计后来的工作如cudaMemcpyAsync将被阻止。但后来的代码断言失败了。
#include <cuda_runtime.h>
#include <stdlib.h>
#include <string.h>
#include <cassert>
#include <unistd.h>
#include <stdio.h>
#define cuda_check(x) \
assert((x) == cudaSuccess)
const size_t size = 1024 * 1024;
static void CUDART_CB cuda_callback(
cudaStream_t, cudaError_t, void* host) {
float* host_A = static_cast<float*>(host);
for (size_t i = 0; i < size; ++i) {
host_A[i] = i;
}
printf("hello\n");
sleep(1);
}
int main(void) {
float* A;
cuda_check(cudaMalloc(&A, size * 4));
float* host_A = static_cast<float*>(malloc(size * 4));
float* result = static_cast<float*>(malloc(size * 4));
memset(host_A, 0, size * 4);
cuda_check(cudaMemcpy(A, host_A, size * 4, cudaMemcpyHostToDevice));
cudaStream_t stream;
cuda_check(cudaStreamCreate(&stream));
cuda_check(cudaStreamAddCallback(stream, cuda_callback, host_A, 0));
cuda_check(cudaMemcpyAsync(A, host_A, size * 4, cudaMemcpyHostToDevice,
stream));
cuda_check(cudaStreamSynchronize(stream));
cuda_check(cudaMemcpy(result, A, size * 4, cudaMemcpyDeviceToHost));
for (size_t i = 0; i < size; ++i) {
assert(result[i] == i);
}
return 0;
}
答案 0 :(得分:1)
你对正在发生的事情的假设并不是真的正确。如果我使用分析器来收集代码的运行时API跟踪(我添加cudaDeviceReset
以确保分析数据被刷新),我看到了:
124.79ms 129.57ms cudaMalloc
255.23ms 694.20us cudaMemcpy
255.93ms 38.881us cudaStreamCreate
255.97ms 123.44us cudaStreamAddCallback
256.09ms 1.00348s cudaMemcpyAsync
1.25957s 76.899us cudaStreamSynchronize
1.25965s 1.3067ms cudaMemcpy
1.26187s 71.884ms cudaDeviceReset
正如您所看到的,cudaMemcpyAsync
确实被回调阻止了(完成时花了> 1.0秒)。
复制未能按照您认为的顺序发生的事实可能是由于您使用常规可分页主机分配,而不是固定内存并期望回调立即触发空队列。重要的是要注意,注册流回调和启动副本的时间少于0.1毫秒,并且回调可能不会立即触发(假设它在另一个线程中),这样就有可能复制在回调函数对空队列条件作出反应之前启动。
有趣的是,如果我将host_A
更改为固定分配并运行代码,我会获得此API时间轴:
124.21ms 130.24ms cudaMalloc
254.45ms 1.0988ms cudaHostAlloc
255.98ms 376.14us cudaMemcpy
256.36ms 33.841us cudaStreamCreate
256.39ms 87.303us cudaStreamAddCallback
256.48ms 17.208us cudaMemcpyAsync
256.50ms 1.00331s cudaStreamSynchronize
1.25981s 1.2880ms cudaMemcpy
1.26205s 68.506ms cudaDeviceReset
现在请注意,cudaStreamSynchronize
是被阻止的呼叫。但是在这种情况下,程序会传递断言,这可能与调度程序正确地管理主机内存固定的流中的依赖关系有关。