并行前缀和 - 最快的实现
Posted
技术标签:
【中文标题】并行前缀和 - 最快的实现【英文标题】:Parallel prefix sum - fastest Implementation 【发布时间】:2012-04-07 10:21:21 【问题描述】:我想使用 C++ 实现并行前缀和算法。我的程序应该采用输入数组x[1....N]
,它应该在数组y[N]
中显示输出。 (注意N的最大值为1000。)
到目前为止,我浏览了许多研究论文,甚至是***中的算法。 但我的程序还应该显示输出、步骤以及每个步骤的操作/指令。
我想要最快的实现,就像我想要最小化操作数量和步骤一样。
例如::
x = 1, 2, 3, 4, 5, 6, 7, 8 - Input
y = ( 1, 3, 6, 10, 15, 21, 28, 36) - Output
但是除了显示 y 数组作为输出之外,我的程序还应该显示每个步骤的操作。我也参考了这个帖子calculate prefix sum,但可以从中得到很多帮助。
【问题讨论】:
您的具体问题是什么?这似乎是一个非常简单的算法就足够了。 @ Niklas B::我实际上想要,我的程序应该使用最小步数和最小操作数。比如如果 N 是 1000,我的程序应该使用小于 20 的步数和小于 2100 的操作数。 尝试自己写一个!只需将数字相加即可。 @Niklas B :他想要“并行”前缀和算法。 你实现了***文章中关于并行前缀和的算法吗?如果是这样,请在此处或在ideone上发布,我们将帮助您完成“显示输出、步骤以及每个步骤的操作/说明”部分。 【参考方案1】:这个问题的答案在这里:Parallel Prefix Sum (Scan) with CUDA 和这里:Prefix Sums and Their Applications。 NVidia 文章提供了使用 CUDA GPU 的最佳实现,卡内基梅隆大学 PDF 论文解释了该算法。我还使用 MPI 实现了 O(n/p) 前缀和,您可以在此处找到:In my github repo。
这是通用算法的伪代码(与平台无关):
示例 3. 工作效率高的和扫描算法的上扫(减少)阶段(Blelloch 1990 之后)
for d = 0 to log2(n) – 1 do
for all k = 0 to n – 1 by 2^(d+1) in parallel do
x[k + 2^(d+1) – 1] = x[k + 2^d – 1] + x[k + 2^(d+1) – 1]
示例 4. 高效并行和扫描算法的向下扫描阶段(Blelloch 1990 之后)
x[n – 1] = 0
for d = log2(n) – 1 down to 0 do
for all k = 0 to n – 1 by 2^(d+1) in parallel do
t = x[k + 2^d – 1]
x[k + 2^d – 1] = x[k + 2^(d+1) – 1]
x[k + 2^(d+1) – 1] = t + x[k + 2^(d+1) – 1]
其中x是输入数据,n是输入的大小,d是并行度(CPU数量) .这是一个共享内存计算模型,如果它使用分布式内存,您需要向该代码添加通信步骤,就像我在提供的 Github 示例中所做的那样。
【讨论】:
很好的回应,但x[k + 2^d +1 – 1]
应该是 x[k + 2^(d +1) – 1]
在这里和你链接到的 CUDA 文章(我相信他们给出的代码的图表 - 我相信下标符号是误丢在那里)。
你是对的。我查看了 Blelloch 的文章,似乎 NVidia 的文章不正确。
在上述算法的下扫阶段,假设n=10,对于d=2和k=8,索引k+2^d–1>n。对于 k+2^(d+1)–1>n 也是如此。这会导致应用程序核心转储。我们应该如何处理 n 不是 2 的幂的情况?
你需要用 0 填充它,所以它是 2 的幂。
我很难理解for all k = 0 to n – 1 by 2^(d+1) in parallel do
究竟做了什么。我得到了for all k = 0 to n – 1
部分,但by 2^(d+1)
是什么意思?我知道d
来自外部循环,所以可以肯定,我可以计算出2^(d+1)
,但是我应该怎么做,或者by
关键字是什么意思?如果您能澄清这一点,那就太好了。谢谢!【参考方案2】:
我只实现了数组中所有元素的总和(Blelloch 的向上扫描减少部分),而不是在 java/opencl 中使用 Aparapi (https://code.google.com/p/aparapi/) 实现的完整前缀总和。它可在https://github.com/klonikar/trial-aparapi/blob/master/src/trial/aparapi/Reducer.java 获得,它是针对一般块大小(在代码中称为 localBatchSize)而不是 2 编写的。我发现块大小为 8 最适合我的 GPU。
虽然实现有效(求和计算是正确的),但它的性能比顺序求和差得多。在我的core-i7(8核)CPU上,8388608(8MB)个数字的顺序求和大约需要12ms,GPU上的并行执行(NVidia Quadro K2000M with 384 cores)大约需要 100 毫秒。我什至已经优化为仅在计算后传输最终总和,而不是整个数组。如果没有这种优化,它会多花 20 毫秒。该实现似乎是根据@marcel-valdez-orozco 的回答中描述的算法。
【讨论】:
我还实现了比较开放 CL 和线程与直接顺序执行的其他算法,我发现英特尔酷睿 i7 CPU 已经做了一些重大改进。您还需要查看编译选项,如果您想要真正的顺序执行从编译中删除所有优化,即使这样也很难击败顺序求和,CPU 在计算总和方面非常有效,我不知道究竟是什么优化在运行时完成,使其如此之快。【参考方案3】:下面的代码将完成这项工作
void prfxSum()
int *x=0;
int *y=0;
int sum=0;
int num=0;
int i=0;
cout << "Enter the no. of elements in input array : ";
cin >> num;
x = new int[num];
y = new int[num];
while(i < num)
cout << "Enter element " << i+1 << " : ";
cin >> x[i++];
cout << "Output array :- " << endl;
i = 0;
while(i < num)
sum += x[i];
y[i] = sum;
cout << y[i++] << ", ";
delete [] x;
delete [] y;
以下是执行时的输出
Enter the no. of elements in input array : 8
Enter element 1 : 1
Enter element 2 : 2
Enter element 3 : 3
Enter element 4 : 4
Enter element 5 : 5
Enter element 6 : 6
Enter element 7 : 7
Enter element 8 : 8
Output array :-
1, 3, 6, 10, 15, 21, 28, 36
您可以通过从文件中提供数据来避免用户输入数组 x[] 的 1000 个元素。
【讨论】:
这到底是怎么一回事?这完全是顺序的。我不知道为什么 OP 接受了这个作为答案。以上是关于并行前缀和 - 最快的实现的主要内容,如果未能解决你的问题,请参考以下文章