32

我正在尝试并行化一个非常简单的 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;
}

我用它编译

g++ -O3 testMP.cc -o testMP  -I /opt/boost_1_48_0/include

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

real    0m18.417s
user    0m18.357s
sys     0m0.004s

当我使用“-fopenmp”时,

g++ -O3 -fopenmp testMP.cc -o testMP  -I /opt/boost_1_48_0/include

我得到了这些数字:

real    0m6.853s
user    0m52.007s
sys     0m0.008s

这对我来说没有意义。为什么使用八核只能使性能提高 3 倍?我是否正确编码循环?

4

4 回答 4

38

您应该对and使用 OpenMPreduction子句: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;
}

每个线程在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

见 - 超线性加速:)

于 2012-08-02T08:30:25.807 回答
30

让我们尝试了解如何使用 OpenMP 并行化简单的 for 循环

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

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

在此处输入图像描述

第一

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

最后

  • 线程必须在工作共享结构结束时等待
于 2016-05-02T16:13:02.990 回答
7

因为这个问题被高度评价,所以我决定添加一点 OpenMP 背景来帮助那些访问它的人


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

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

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

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

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

此外

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

或非正式地,#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;
}

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

为了使其更具说明性,使用带有 a和static4的线程会导致如下结果:#pragma omp parallel forchunk_size=1 schedule

在此处输入图像描述

在代码方面,循环将被转换为逻辑上类似于:

for(int i=omp_get_thread_num(); i < n; i+=omp_get_num_threads())
{  
    c[i]=a[i]+b[i];
}

其中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_ID范围从0TOTAL_THREADS - 1,表示在TOTAL_THREADS并行区域上创建的团队的线程总数。

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

     x += rand_g1;
     y += rand_g2;

要解决这种竞争条件,您可以使用 OpenMP 的缩减子句:

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

非正式地,reduction 子句将为每个线程创建变量“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;
}
于 2021-04-05T15:11:05.467 回答
-3

您最多可以实现(!)是线性加速。现在我不记得哪个是来自 linux 的时间,但我建议您使用 time.h 或(在 c++ 11 中)“chrono”并直接从程序测量运行时。最好将整个代码打包到一个循环中,运行 10 次并平均得到 prog 的大约运行时间。

此外,您还遇到了 x,y 的问题——它不符合并行编程中数据局部性的范式。

于 2012-08-02T07:51:40.400 回答