如何用CUDA流重叠thrust::sort_by_key与主机到设备数据拷贝?
嘿,我之前也踩过Thrust流同步的坑!你说的这个情况,核心问题其实是Thrust的执行策略和流的绑定方式不对,导致你以为的异步操作其实悄悄同步了主机端,或者GPU根本没机会把拷贝和排序重叠起来。咱们来一步步解决:
为什么你的重叠没生效?
- Thrust默认执行策略是同步的:哪怕你给
thrust::sort_by_key传了cudaStream_t参数,如果没显式指定流感知的并行策略,Thrust还是会用默认的同步模式——这意味着它会隐式调用cudaStreamSynchronize,导致主机端等待排序完成,自然没法和拷贝重叠。 - 异步拷贝的前提没满足:如果你的H2D拷贝用的是同步的
cudaMemcpy,那主机端会先等拷贝完再执行排序,根本没重叠的机会;另外,异步拷贝必须用页锁定内存(比如cudaHostAlloc分配的,或者Thrust的thrust::host_vector搭配thrust::cuda::host_allocator),不然cudaMemcpyAsync会自动退化为同步拷贝。 - 流的使用方式不对:如果拷贝和排序都用同一个流,GPU会按顺序执行这两个操作,不可能重叠——只有不同流的操作才能被GPU调度并行执行。
- 数据依赖问题:如果你的排序操作依赖于刚拷贝完成的数据,那GPU会自动等待拷贝结束再执行排序,这种场景下本身就没法重叠(重叠只适用于无依赖的操作,比如前一批数据的排序和后一批数据的拷贝)。
正确的重叠实现步骤
1. 显式指定Thrust的流感知执行策略
你需要用thrust::cuda::par.on(stream)来告诉Thrust要在指定流上异步执行操作,而不是默认的同步模式。比如:
// 正确的调用方式:绑定到目标流的并行策略 thrust::sort_by_key( thrust::cuda::par.on(sort_stream), // 关键:指定流感知策略 keys_d.begin(), keys_d.end(), vals_d.begin() );
2. 使用异步拷贝+页锁定内存
确保你的主机内存是页锁定的,并且用cudaMemcpyAsync发起异步拷贝:
// 用Thrust分配页锁定主机内存(比原生CUDA更方便) thrust::host_vector<int, thrust::cuda::host_allocator<int>> keys_h(N); thrust::host_vector<float, thrust::cuda::host_allocator<float>> vals_h(N); // 填充主机数据... // 异步H2D拷贝,绑定到copy_stream cudaMemcpyAsync( keys_d.data(), keys_h.data(), sizeof(int)*N, cudaMemcpyHostToDevice, copy_stream ); cudaMemcpyAsync( vals_d.data(), vals_h.data(), sizeof(float)*N, cudaMemcpyHostToDevice, copy_stream );
3. 用不同的流分离拷贝和排序操作
创建两个独立的流,一个负责拷贝,一个负责排序,这样GPU就能同时处理这两个无依赖的任务:
// 创建两个独立的流 cudaStream_t copy_stream, sort_stream; cudaStreamCreate(©_stream); cudaStreamCreate(&sort_stream); // 发起异步拷贝(在copy_stream中执行) // ...(上面的cudaMemcpyAsync代码) // 发起异步排序(在sort_stream中执行) // ...(上面的thrust::sort_by_key代码) // 主机端可以先执行其他逻辑,最后再等待流完成 cudaStreamSynchronize(copy_stream); cudaStreamSynchronize(sort_stream); // 销毁流 cudaStreamDestroy(copy_stream); cudaStreamDestroy(sort_stream);
4. 用cudaEvent准确计时
如果你之前用主机端的clock()或std::chrono计时,可能会因为异步操作的主机端返回特性导致结果不准。用cudaEvent可以测量GPU操作的真实耗时:
cudaEvent_t start_copy, end_copy, start_sort, end_sort; cudaEventCreate(&start_copy); cudaEventCreate(&end_copy); cudaEventCreate(&start_sort); cudaEventCreate(&end_sort); cudaEventRecord(start_copy, copy_stream); // 发起拷贝... cudaEventRecord(end_copy, copy_stream); cudaEventRecord(start_sort, sort_stream); // 发起排序... cudaEventRecord(end_sort, sort_stream); // 等待所有GPU操作完成 cudaDeviceSynchronize(); float copy_time, sort_time; cudaEventElapsedTime(©_time, start_copy, end_copy); cudaEventElapsedTime(&sort_time, start_sort, end_sort); // 如果重叠生效,两个耗时会接近(而非相加) printf("拷贝耗时: %.2f ms\n", copy_time); printf("排序耗时: %.2f ms\n", sort_time);
最后检查一个细节
确保你的GPU支持并发内核执行(大多数现代GPU都支持,可以用cudaDeviceGetAttribute查询cudaDevAttrConcurrentKernels属性),否则即便用了不同流,GPU也会串行执行操作。
内容的提问来源于stack exchange,提问作者tdoublep
相关产品推荐
相关产品推荐

