我反复排队一系列内核:
for 1..100:
for 1..10000:
// Enqueue GPU kernels
Kernel 1 - update each element of array
Kernel 2 - sort array
Kernel 3 - operate on array
end
// run some CPU code
output "Waiting for GPU to finish"
// copy from device to host
cudaMemcpy ... D2H(array)
end
内核3的顺序为O(N ^ 2),因此到目前为止是最慢的。对于内核2,我直接在设备上使用thrust :: sort_by_key:
thrust::device_ptr<unsigned int> key(dKey);
thrust::device_ptr<unsigned int> value(dValue);
thrust::sort_by_key(key,key+N,value);
似乎这种对推力的调用是阻塞的,因为只有在内部循环完成后才会执行CPU代码。我看到了这一点,因为如果我删除对sort_by_key
的调用,主机代码(正确)在内循环结束前输出“Waiting”字符串,而如果我运行排序则不会。
有没有办法异步调用thrust::sort_by_key
?
答案 0 :(得分:2)
首先考虑有一个内核启动队列,它只能容纳如此多的挂起启动。一旦启动队列已满,任何类型的其他内核启动都将被阻止。在空队列插槽可用之前,主机线程将不会继续(超出那些启动请求)。我很确定3次内核启动的10000次迭代将在它达到10000次迭代之前填充此队列。因此,如果您按顺序启动30000个内核,那么任何类型的非平凡内核启动都会有一些延迟(我认为)。 (最终,当所有内核都添加到队列中,因为有些内核已经完成,那么在所有内核实际完成之前,如果没有其他阻塞行为,你会看到“等待...”消息。)
thrust::sort_by_key
requires temporary storage(大小约等于您的数据集大小)。每次使用时,都会通过cudaMalloc
操作分配此临时存储。此cudaMalloc
操作是阻止。从主机线程启动cudaMalloc
时,它会等待内核活动中的间隙,然后才能继续。
要解决第2项,似乎可能至少有两种可能的方法:
提供thrust custom allocator。根据此分配器的特性,您可以消除阻塞cudaMalloc
行为。 (但见下面的讨论)
使用cub SortPairs。这里的优势(我认为 - 你的例子不完整)是你可以做一次分配(假设你知道整个循环迭代中最坏的临时存储大小)并且不需要在你的内部进行临时内存分配环。
据我所知,推力方法(上面的1)在每次迭代时仍会有效地进行某种临时分配/自由步骤,即使你提供自定义分配器。如果你有一个设计良好的自定义分配器,那么它可能几乎是一个“无操作”。 cub方法似乎具有需要知道最大大小的缺点(为了完全消除分配/自由步骤的需要),但我认为对于推力自定义分配器将具有相同的要求。否则,如果你需要在某个时刻分配更多的内存,那么自定义分配器实际上必须做一些像cudaMalloc
这样的事情,这会给工作带来麻烦。