在openmp中并行循环

dsi*_*ign 22 c++ parallel-processing openmp

我正在尝试并行化一个非常简单的for循环,但这是我在很长一段时间内第一次尝试使用openMP.我对运行时间感到困惑.这是我的代码:

#include <vector>
#include <algorithm>

using namespace std;

int main () 
{
    int n=400000,  m=1000;  
    double x=0,y=0;
    double s=0;
    vector< double > shifts(n,0);


    #pragma omp parallel for 
    for (int j=0; j<n; j++) {

        double r=0.0;
        for (int i=0; i < m; i++){

            double rand_g1 = cos(i/double(m));
            double rand_g2 = sin(i/double(m));     

            x += rand_g1;
            y += rand_g2;
            r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
        }
        shifts[j] = r / m;
    }

    cout << *std::max_element( shifts.begin(), shifts.end() ) << endl;
}
Run Code Online (Sandbox Code Playgroud)

我用它编译它

g++ -O3 testMP.cc -o testMP  -I /opt/boost_1_48_0/include
Run Code Online (Sandbox Code Playgroud)

也就是说,没有"-fopenmp",我得到了这些时间:

real    0m18.417s
user    0m18.357s
sys     0m0.004s
Run Code Online (Sandbox Code Playgroud)

当我使用"-fopenmp"时,

g++ -O3 -fopenmp testMP.cc -o testMP  -I /opt/boost_1_48_0/include
Run Code Online (Sandbox Code Playgroud)

我得到了这些数字:

real    0m6.853s
user    0m52.007s
sys     0m0.008s
Run Code Online (Sandbox Code Playgroud)

这对我没有意义.如何使用八核只能使性能提高3倍?我正确编码循环吗?

Hri*_*iev 25

您应该使用OpenMP reduction子句xy:

#pragma omp parallel for reduction(+:x,y)
for (int j=0; j<n; j++) {

    double r=0.0;
    for (int i=0; i < m; i++){

        double rand_g1 = cos(i/double(m));
        double rand_g2 = sin(i/double(m));     

        x += rand_g1;
        y += rand_g2;
        r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
    }
    shifts[j] = r / m;
}
Run Code Online (Sandbox Code Playgroud)

随着reduction每个线程在其中累积其自己的部分和,x并且y最后将所有部分值相加在一起以便获得最终值.

Serial version:
25.05s user 0.01s system 99% cpu 25.059 total
OpenMP version w/ OMP_NUM_THREADS=16:
24.76s user 0.02s system 1590% cpu 1.559 total
Run Code Online (Sandbox Code Playgroud)

看 - 超线性加速:)


dre*_*ash 17

因为这个问题受到高度关注,所以我决定添加一些 OpenMP 背景来帮助访问它的人


创建#pragma omp parallel一个由一组 组成的并行区域threads,其中每个线程执行 所包含的整个代码块parallel region。从OpenMP 5.1中可以读到更正式的描述:

当线程遇到并行构造时,会创建一组线程来执行并行区域 (..)。遇到并行构造的线程成为新团队的主线程,在新并行区域的持续时间内线程数为零。新团队中的所有线程(包括主线程)都会执行该区域。创建团队后,团队中的线程数在该并行区域的持续时间内保持不变。

创建#pragma omp parallel fora parallel region(如前所述),并且threads将使用 ,将其包含的循环的迭代分配给该区域的default chunk size,并且通常default schedule为。但请记住,标准的不同具体实施之间可能会有所不同。 staticdefault scheduleOpenMP

OpenMP 5.1中您可以阅读更正式的描述:

工作共享循环构造指定一个或多个关联循环的迭代将由团队中的线程在其隐式任务的上下文中并行执行。迭代分布在团队中已存在的线程中,该团队正在执行工作共享循环区域绑定到的并行区域

而且

并行循环构造是一种指定并行构造的快捷方式,该并行构造包含具有一个或多个关联循环且没有其他语句的循环构造。

或者非正式地,是构造函数与#pragma omp parallel for的组合。就您而言,这意味着:#pragma omp parallel#pragma omp for

#pragma omp parallel for 
for (int j=0; j<n; j++) {

    double r=0.0;
    for (int i=0; i < m; i++){

        double rand_g1 = cos(i/double(m));
        double rand_g2 = sin(i/double(m));     

        x += rand_g1;
        y += rand_g2;
        r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
    }
    shifts[j] = r / m;
}
Run Code Online (Sandbox Code Playgroud)

将创建一组线程,并将最外层循环的迭代块分配给每个线程。

为了使其更具说明性,对于4线程来说,#pragma omp parallel for带有 achunk_size=1static 的 schedule结果将类似于:

注意:图片上的“i”指的是代码中的“j”)

在此输入图像描述

从代码角度来看,循环将转换为逻辑上类似于以下内容的内容:

for(int j=omp_get_thread_num(); j < n; j+=omp_get_num_threads())
{  
    c[j]=a[j]+b[j];
}
Run Code Online (Sandbox Code Playgroud)

其中omp_get_thread_num()

omp_get_thread_num 例程返回当前组内调用线程的线程号。

omp_get_num_threads()

返回当前组中的线程数。在程序的连续部分中,omp_get_num_threads 返回 1。

或者换句话说,for(int i = THREAD_ID; i < n; i += TOTAL_THREADS). 范围THREAD_ID0TOTAL_THREADS - 1TOTAL_THREADS代表在并行区域上创建的团队线程总数。

有了这些知识,并查看您的代码,人们可以看到您在变量“x”和“y”的更新上存在竞争条件。这些变量在线程之间共享并在并行区域内更新,即:

     x += rand_g1;
     y += rand_g2;
Run Code Online (Sandbox Code Playgroud)

要解决此竞争条件,您可以使用 OpenMP 的归约子句:

指定每个线程私有的一个或多个变量是并行区域末尾的归约操作的主题。

非正式地,归约子句将为每个线程创建变量“x”和“y”的私有副本,并在并行区域的末尾将所有这些“x”和“y”变量之间的求和添加到原始变量中来自初始线程的“x”和“y”变量。

#pragma omp parallel for reduction(+:x,y)
for (int j=0; j<n; j++) {

    double r=0.0;
    for (int i=0; i < m; i++){

        double rand_g1 = cos(i/double(m));
        double rand_g2 = sin(i/double(m));     

        x += rand_g1;
        y += rand_g2;
        r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
    }
    shifts[j] = r / m;
}
Run Code Online (Sandbox Code Playgroud)


Bas*_*ANI 14

让我们试着理解如何使用OpenMP并行化简单循环

#pragma omp parallel
#pragma omp for
    for(i = 1; i < 13; i++)
    {
       c[i] = a[i] + b[i];
    }
Run Code Online (Sandbox Code Playgroud)

假设我们有3可用的线程,这就是将要发生的事情

在此输入图像描述

首先

  • 为线程分配一组独立的迭代

最后

  • 线程必须在工作共享构造结束时等待