我遇到以下问题:我需要根据GPU上的树结构计算值的包容性扫描(例如prefix sums)。这些扫描来自根节点(自上而下)或叶节点(自下而上)。简单链的情况是easily handled,但树结构使并行化很难有效地实现。
例如,在自上而下的包容性扫描后,(12)
将保留(0)[op](6)[op](7)[op](8)[op](11)[op](12)
,对于自下而上的包含扫描,(8)
将保留(8)[op](9)[op](10)[op](11)[op](12)
,其中[op]
是给定的二元运算符(矩阵加法,乘法等)。
还需要考虑以下几点:
(6)
作为新的根)。尽管如此,典型的树不应该太不平衡。在这种情况下,什么是“最佳”数据结构(对于树结构)和最佳算法(对于包容性扫描/前缀总和)来解决这个问题?
答案 0 :(得分:3)
可能是一个难以理解的想法,但想象一下,你将0值的节点插入到树中,这样你就可以得到一个2D矩阵。例如,在您的示例中, 5 节点下方将有3个零值节点。然后使用一个线程水平移动矩阵的每个级别。对于自上而下的前缀sum,以这样一种方式偏移线程,即每个底线延迟树在该位置可以具有的最大分支数。因此,你会得到一个“波浪”,在矩阵上方有一条倾斜的边缘。进一步沿着上面的线程及时计算这些节点,以便进一步向下运行的线程进一步处理它们。您需要与树深度相同的线程数。
答案 1 :(得分:3)
我认为并行前缀扫描可能不适合您的情况,因为:
并行前缀扫描算法将增加[op]
的总数,在prefix sum的链接中,16输入并行前缀扫描需要26 [op]
,而顺序版本只需要15.并行算法执行得更好是基于假设有足够的硬件资源并行运行多个[op]
。
您可以在尝试并行前缀扫描之前评估[op]
的费用。
另一方面,由于树的大小很小,我认为你可以简单地将你的树视为4(叶子节点的数量)独立的简单链,并使用并发内核执行来提高这些4的性能。前缀扫描内核
0-1-2-3
0-4-5
0-6-7-8-9-10
0-6-7-8-11-12
答案 2 :(得分:0)
我认为在Kepler GK110架构中,您可以递归地调用内核,它们称之为动态并行。因此,如果您需要计算树的每个节点的值的总和,动态并行性将有所帮助。但是,递归的深度可能是一个约束。
答案 3 :(得分:0)
我的第一印象是你可以在一维数组中组织树节点,类似于Eric所建议的。然后你可以对该阵列进行分段前缀和扫描(http://en.wikipedia.org/wiki/Segmented_scan)。
以树节点为例,1-dim数组如下所示:
0-1-2-3-0-4-5-0-6-7-8-9-10-0-6-7-8-11-12
然后你会有一个并行的标志数组,指示新列表的开始位置(按列表我的意思是从根开始到叶节点结束的序列):
1-0-0-0-1-0-0-1-0-0-0-0- 0-1-0-0-0- 0- 0
对于自下而上的情况,您可以创建一个单独的segment-flag数组,如下所示:
0-0-0-1-0-0-1-0-0-0-0-0- 1-0-0-0-0- 0- 1
并使用相同的算法以相反的顺序遍历它。
至于如何实现分段前缀扫描,我自己没有实现,但我发现了一些可能提供有关如何操作信息的参考文献:http://www.cs.cmu.edu/afs/cs/academic/class/15418-s12/www/lectures/24_algorithms.pdf和http://www.cs.ucdavis.edu/research/tech-reports/2011/CSE-2011-4.pdf(参见第23页)