如何在CUDA上实现深度递归

时间:2013-01-14 00:42:42

标签: cuda

在CUDA上实现深度递归的最有效方法是什么(数千个级别), 如果递归用于遍历树状数据结构,那么在哪里找到代码示例?

我刚刚使用Cuda Dynamic Parallelism在K20 GPU上实现了递归,但发现由于参数限制了24个级别 cudaLimitDevRuntimeSyncDepth

我想达到最高。大数据的速度和缩放。

1 个答案:

答案 0 :(得分:7)

根据我的经验,在CUDA中管理递归的最可靠和有效的方法是手动管理递归堆栈并“扁平化”函数。例如,如果您正在遍历二叉树,则它看起来像这样:

while (!stack.isEmpty()) {
  Node n = stack.pop();
  ... //do stuff with n
  if (!n.isLeaf()) {
    stack.push(n.left());
    stack.push(n.right());
  } 
}

上述技术可以帮助任何代码(CUDA或单线程CPU)。由于您不想使用STL,因此必须由您实现堆栈功能。


下一步 - 更具体的CUDA - 将评估每个节点是否需要由单独的线程处理,或者可能是整个warp或块甚至整个网格都可以分配给它。根据这一点,stack应该位于本地,共享或全局内存空间中,并且其成员函数应该在相应的执行单元(线程/块/网格)上统一运行。

请注意,如果你想在本地内存中使用每个线程stack,你将使用大量内存(10000个线程x 1000个最大深度递归),你可能会遇到很多线程分歧,从而降低你的性能。

另一方面---每个块stack将需要更少的内存,但需要__syncthreads()

如果每个节点有足够的并行工作,我强烈建议对节点进行每个warp或每个块的处理。


最后,如果你在共享内存中有堆栈,但是你发现需要每个warp工作,你可以考虑对pushpop使用原子操作,并引入工作窃取技术来平衡你的经线之间的工作更好。 如果您需要通过在全局内存中使用单个堆栈来进行每节点块处理,也可以进行工作窃取。


修改 如果您需要向上走,在处理完树后,您可以稍后向上推入树中。

struct StackEntry {
    Node* node;
    bool goingUp;
};

while (!stack.isEmpty()) {
  StackEntry entry = stack.pop();
  ... //do stuff with entry.node
  if (!entry.goingUp && !entry.node->isLeaf()) {
    stack.push(StackEntry(entry.node->left(),false));
    stack.push(StackEntry(entry.node->right(),false));
    stack.push(StackEntry(entry.node,true));
  } 
}

假设每个节点都有一个指向其父节点的指针(或者你可以在StackEntry struct中引入这样的指针),你可以将参数传递给树。

请注意,这会引入堆栈中条目之间的依赖关系。只要只有一个执行单元(线程/块/网格)从堆栈中推送/弹出,这就没问题了。但是,如果许多执行程序共享一个堆栈,使用前面讨论的工作窃取算法,它可能会破坏依赖关系。必须另外考虑以防止这种情况。

您可能想要重新组织StackEntry正在存储的内容以及何时将元素推入堆栈。上述方法不是唯一的方法!