从串行到omp:无加速

时间:2019-05-30 17:25:52

标签: c++ openmp

我是openMP的新手。我正在研究全对最短路径算法,这是我需要并行化的串行C ++例程(帖子末尾的完整代码):

void mini(vector<vector<double>> &M, size_t n, vector<double> &rowk, vector<double> &colk)
{
    size_t i, j;

    for ( i=0; i<n; i++)
        for ( j=0; j<n; j++)
            M[i][j]=min(rowk[j]+colk[i], M[i][j]);

}

在执行时我得到了这个:

$ time ./floyd 

real    0m0,349s
user    0m0,349s
sys     0m0,000s

现在,我尝试插入一些指令:

void mini(vector<vector<double>> &M, size_t n, vector<double> &rowk, vector<double> &colk)
{

    #pragma omp parallel
    {
        size_t i, j;

        #pragma omp parallel for
        for ( i=0; i<n; i++)
            for ( j=0; j<n; j++)
                M[i][j]=min(rowk[j]+colk[i], M[i][j]);
    }

}

不幸的是,没有加速:

$ grep -c ^processor /proc/cpuinfo                                    
4
$ time ./floyd 

real    0m0,547s
user    0m2,073s
sys     0m0,004s

我在做什么错了?

编辑

处理器:Intel(R)CoreTM i5-4590 CPU(4个硬件内核)

完整代码:

#include <cstdio>
#include <vector>
#include <limits>
#include <ctime>
#include <random>
#include <set>
#include <omp.h>

using namespace std;

typedef struct Wedge
{
    int a, b;
    double w;
} Wedge;


typedef pair<int, int> edge;

int randrange(int end, int start=0)
{
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dis(start, end-1);

    return dis(gen);
}

void relax_omp(vector<vector<double>> &M, size_t n, vector<double> &rowk, vector<double> &colk)
{
    #pragma omp parallel
    {
        size_t i, j;

        #pragma omp parallel for
        for (i=0; i<n; i++)
            for ( j=0; j<n; j++)
                M[i][j]=min(rowk[j]+colk[i], M[i][j]);

    }
}


void relax_serial(vector<vector<double>> &M, size_t n, vector<double> &rowk, vector<double> &colk)
{
    size_t i, j;


    for (i=0; i<n; i++)
        for ( j=0; j<n; j++)
            M[i][j]=min(rowk[j]+colk[i], M[i][j]);
}


void floyd(vector<vector<double>> &dist, bool serial)
{
    size_t i, k;

    size_t n {dist.size()};

    for (k=0; k<n; k++)
    {
        vector<double> rowk =dist[k];
        vector<double> colk(n);

        for (i=0; i<n; i++)
            colk[i]=dist[i][k];
        if (serial)
            relax_serial(dist, n, rowk, colk);
        else
            relax_omp(dist, n, rowk, colk);
    }

    for (i=0; i<n; i++)
        dist[i][i]=0;
}


vector<Wedge> random_edges(int n, double density, double max_weight)
{
    int M{n*(n-1)/2};
    double m{density*M};
    set<edge> edges;
    vector<Wedge> wedges;

    while (edges.size()<m)
    {
        pair<int,int> L;
        L.first=randrange(n);
        L.second=randrange(n);

        if (L.first!=L.second && edges.find(L) == edges.end())
        {
            double w=randrange(max_weight);
            Wedge wedge{L.first, L.second, w};
            wedges.push_back(wedge);
            edges.insert(L);
        }
    }
    return wedges;
}


vector<vector<double>> fill_distances(vector<Wedge> wedges, int n)
{
    double INF = std::numeric_limits<double>::infinity();
    size_t i, m=wedges.size();
    vector<vector<double>> dist(n, vector<double>(n, INF));
    int a, b;
    double w;
    for (i=0; i<m; i++)
    {   a=wedges[i].a;
        b=wedges[i].b;
        w=wedges[i].w;
        dist[a][b]=w;
    }
    return dist;
}


int main (void)
{
    double density{0.33};
    double max_weight{200};
    int n{800};
    bool serial;

    int ntest=10;
    double avge_serial=0, avge_omp=0;

    for (int i=0; i<ntest; i++)
    {
        vector<Wedge> wedges=random_edges(n, density, max_weight);
        vector<vector<double>> dist=fill_distances(wedges, n);
        double dtime;

        dtime = omp_get_wtime();
        serial=true;
        floyd(dist, serial);
        dtime = omp_get_wtime() - dtime;
        avge_serial+=dtime;


        dtime = omp_get_wtime();
        serial=false;
        floyd(dist, serial);
        dtime = omp_get_wtime() - dtime;
        avge_omp+=dtime;
    }
    printf("%d tests, n=%d\n", ntest, n);
    printf("Average serial : %.2lf\n", avge_serial/ntest);
    printf("Average openMP : %.2lf\n", avge_omp/ntest);
    return 0;
}

输出:

20 tests, n=800
Average serial : 0.31
Average openMP : 0.61

命令行:

g++ -std=c++11 -Wall -O2 -Wno-unused-result -Wno-unused-variable -Wno-unused-but-set-variable -Wno-unused-parameter floyd.cpp -o floyd -lm -fopenmp

2 个答案:

答案 0 :(得分:1)

这可能有很多原因,最明显的是工作量太小而无法加快速度。初始工作负载为300ms。我建议将其封装在一个串行外部循环中,该循环至少重复此工作20次,然后您才能以(300ms * 20)6秒的串行时间开始进行测试。

另一个因素是在运行此功能的计算机上并行内核的可用性。如果您的cpu具有一个内核,由于线程切换的成本,多线程将导致速度变慢。 2个逻辑核应显示出一定的加速,2个物理核应显示出接近线性的加速。

单独使用编译指示也不能保证使用openMP。您必须使用-fopenmp命令行参数进行编译,以确保openmp库链接到您的目标代码。

修改

现在看您的代码,控制工作量的因素似乎是N,而不是外部循环。外循环的想法是在相同的时间段内人为地增加工作量,但这在您尝试解决特定问题时无法做到。您也可以尝试并行化嵌套循环,但是我认为N = 800对于并行化而言太低了。

#pragma omp parallel for private(j) collapse(2)

j必须对外部循环的每次迭代都是私有的,因此为private(j),否则j将在所有线程之间共享,从而导致结果不准确。

您的循环执行了640,000次,这对于运行3GHZ +时钟的现代CPU来说并不算多,请尝试在N = 5000左右执行25M迭代。

答案 1 :(得分:1)

您的主要问题是您不小心使用了嵌套并行性:

#pragma omp parallel
{
    size_t i, j;

    #pragma omp parallel for

由于您已经处于平行区域,因此第二行应该是

    #pragma omp for

否则,由于omp parallel for等于omp parallelomp for,因此您有两个嵌套的并行区域,这通常是不好的。修复此小问题,在类似的CPU上速度提高了约2倍。

有几个限制使您不太可能获得4倍的全速运行,例如但不限于:

  • 内存带宽成为瓶颈
  • 由于并行循环内完成的工作量少而产生的相对开销
  • 在turbo模式下通过多个线程降低时钟频率

编辑: 顺便说一下,编写代码的惯用方式如下:

void relax_omp(...) {
  #pragma omp parallel for
  for (size_t i=0; i<n; i++) {
    for (size_t j=0; j<n; j++) {
      M[i][j]=min(rowk[j]+colk[i], M[i][j]);
    }
  }
}

如果您尽可能在局部声明变量,则OpenMP几乎总是做正确的事情。在这种情况下,这意味着ij是私有的。通常,以这种方式推理代码要容易得多。