加速总结循环的多线程 C++ 程序
Posted
技术标签:
【中文标题】加速总结循环的多线程 C++ 程序【英文标题】:Multithread C++ program to speed up a summatory loop 【发布时间】:2013-06-14 16:18:30 【问题描述】:我有一个从 1 迭代到 N 的循环,并随着时间的推移取模和。但是 N 非常大,所以我想知道是否有办法通过利用多线程来修改它。
给出示例程序
for (long long i = 1; i < N; ++i)
total = (total + f(i)) % modulus;
f(i) 在我的例子中不是一个实际的函数,而是一个很长的表达式,会占用这里的空间。放在那里是为了说明目的。
【问题讨论】:
如果你真的想加快速度尝试使用 cuda 也可以问一下这个循环的目的是什么 一个非常简单的优化 - 最后保存 % 模数。从算术上讲,没有理由在循环内执行此操作,除非您这样做是为了防止溢出。 (感谢@Xaqq 抓住了我之前过于宽泛的概括。) @ScottMermelstein 也许没有它会溢出。但是,是的,如果你能把它留到最后,那就去做吧。 会溢出,需要模数。 是的,给每个线程自己的总数,让他们每个人总结独特的范围,然后当他们都完成并加入时,添加总数。 【参考方案1】:是的,试试这个:
double total=0;
#pragma omp parallel for reduction(+:total)
for (long long i = 1; i < N; ++i)
total = (total + f(i)) % modulus;
编译:
g++ -fopenmp your_program.c
就这么简单!不需要标题。 #pragma
行自动启动几个线程,平均划分循环的迭代,然后在循环之后重新组合所有内容。但请注意,您必须事先知道迭代次数。
此代码使用OpenMP,它提供了非常适合您的情况的易于使用的并行性。 OpenMP 甚至内置于 GCC 和 MSVC compilers。
This page 显示了一些其他可能的归约操作。
如果你需要嵌套for循环,你可以写
double total=0;
#pragma omp parallel for reduction(+:total)
for (long long i = 1; i < N; ++i)
for (long long j = 1; j < N; ++j)
total = (total + f(i)*j) % modulus;
外部循环将被并行化,每个线程运行自己的内部循环副本。
但你也可以使用collapse 指令:
#pragma omp parallel for reduction(+:total) collapse(2)
然后两个循环的迭代将被自动分割。
如果每个线程都需要在循环之前定义的自己的变量副本,请使用private
命令:
double total=0, cheese=4;
#pragma omp parallel for reduction(+:total) private(cheese)
for (long long i = 1; i < N; ++i)
total = (total + f(i)) % modulus;
请注意,您不需要使用private(total)
,因为reduction
暗示了这一点。
【讨论】:
这是如何工作的?如果总和超过一个双循环,或者除了总计之外还涉及多个变量怎么办? 如果总和超过双循环,则外部循环的迭代将在线程之间划分,每个线程将运行自己的内部循环副本。 您可以拥有任意数量的reduction
子句,但您应该注意private
和shared
命令,它们将变量隔离到特定线程。
假设我有一个三重循环,i=1 到 A,j=1 到 B,k=1 到 C。将使用您列出的相同 pragma 语句将循环拆分为i=1 到 A 循环?
请注意,在此循环之后您还需要一个模数 total %= modulus
,归约子句只会为您添加每个线程的总数,通常不会介于 0
和 modulus-1
之间【参考方案2】:
假设 f(i) 是独立的,但运行时间大致相同,您可以自己创建 4 个线程,并让每个线程相加总和的 1/4,然后将总和作为值返回,并且加入每一个。这不是一个非常灵活的方法,特别是如果 f(i) 次可以是随机的。
您可能还想考虑一个线程池,让每个线程计算 f(i),然后得到下一个 i 相加。
【讨论】:
这不是一个有效的答案,因为它是对我所要求的内容的改写【参考方案3】:在你的total
http://bisqwit.iki.fi/story/howto/openmp/#ReductionClause 的减少子句中尝试 openMP 的并行处理
【讨论】:
【参考方案4】:如果f(long long int)
是一个仅依赖于其输入且没有全局状态和加法的阿贝尔属性的函数,您可以获得如下显着优势:
for(long long int i = 0, j = 1; i < N; i += 2, j += 2)
total1 = (total1 + f(i)) % modulus;
total2 = (total2 + f(j)) % modulus;
total = (total1 + total2) % modulus;
像这样打破它应该通过允许编译器改进代码生成和 CPU 使用更多资源(这两个操作可以并行处理)和泵出更多数据并减少停顿来帮助. [我在这里假设一个 x86 架构]
当然,在不知道 f
的真实样子的情况下,很难确定这是否可行,或者它是否真的会有所帮助或产生可衡量的差异。
可能还有其他类似的技巧,您可以利用您的输入和平台的特殊知识 - 例如,SSE 指令可以让您做更多的事情。特定于平台的功能也可能有用。例如,可能根本不需要模运算,您的编译器可能会提供一个特殊的内在函数来执行模 N 的加法。
我必须问,您是否分析过您的代码并发现这是一个热点?
【讨论】:
我不知道如何正确地分析代码,但它是一个热点 我想你的意思是i+=2, j+=2
。另外,请注意 OP 循环从 1
开始,尽管这是一个小问题(有些人会说挑剔)【参考方案5】:
你可以使用Threading Building Blocks
tbb::parallel_for(1, N, [=](long long i)
total = (total + f(i)) % modulus;
);
或者没有溢出检查:
tbb::parallel_for(1, N, [=](long long i)
total = (total + f(i));
);
total %= modulus;
【讨论】:
以上是关于加速总结循环的多线程 C++ 程序的主要内容,如果未能解决你的问题,请参考以下文章
如何多线程(多进程)加速while循环(语言-python)?