我是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
答案 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 parallel
和omp for
,因此您有两个嵌套的并行区域,这通常是不好的。修复此小问题,在类似的CPU上速度提高了约2倍。
有几个限制使您不太可能获得4倍的全速运行,例如但不限于:
编辑: 顺便说一下,编写代码的惯用方式如下:
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几乎总是做正确的事情。在这种情况下,这意味着i
和j
是私有的。通常,以这种方式推理代码要容易得多。