加速总结循环的多线程 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 子句,但您应该注意privateshared 命令,它们将变量隔离到特定线程。 假设我有一个三重循环,i=1 到 A,j=1 到 B,k=1 到 C。将使用您列出的相同 pragma 语句将循环拆分为i=1 到 A 循环? 请注意,在此循环之后您还需要一个模数 total %= modulus,归约子句只会为您添加每个线程的总数,通常不会介于 0modulus-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)?

C ++中的多线程文件散列[关闭]

基于 ffmpeg 的多线程 C++ 应用程序解码失败

纯 C++ 中的多线程?

简单的多线程帮助? C++、WaitForSingleObject 和同步

多线程信号[关闭]