如何在 omp 并行中使特定部件串行?

Posted

技术标签:

【中文标题】如何在 omp 并行中使特定部件串行?【英文标题】:How to make specific parts serial within omp parallel? 【发布时间】:2020-03-04 23:56:41 【问题描述】:

假设我有以下代码,其中我有一个进行计算的串行版本和尝试的并行版本:

double getSum(std::vector<double>& xv, std::vector<double>& yv);

int main()

    int nrows = 600; int ncols = 200; int Lvec = 10000;
    std::vector<std::vector<std::vector<double>>> vec3;
    std::vector<std::vector<double>> xarr(nrows, std::vector<double>(ncols, 0));
    srand(time(NULL));

    for (int k = 1; k <= nrows; k++) 
        //vec3 is a 3d vector, where each component stores
        //2d vectors of variable sizes determined by ncols1, 
        //which ranges from 0-180
        int ncols1= (rand() % 10) *20;
        std::vector<std::vector<double>> vecCol;

        for (int j = 1; j <= ncols1; j++) 
            std::vector<double> vec0;
            for (int m = 1; m <= Lvec; m++) 
                vec0.push_back((rand() % 10) / 10.0);
            
            vecCol.push_back(vec0);
        
        vec3.push_back(vecCol);
    


    //serial version
    std::vector<std::vector<double>> xarrSerial(nrows, std::vector<double>(ncols, 0));
    double xnow, xprev;
    double t0 = -omp_get_wtime();
    for (int k = 1; k <= nrows; k++) 
        std::vector<std::vector<double>> vecCol = vec3[k - 1];
        for (int j = 1; j <= vecCol.size(); j++) 
            if (j == 1) 
                xprev = 0;
                xarrSerial[k - 1][j - 1] = xprev;
            
            else 
                xnow = getSum(vec3[k - 1][j - 2], vec3[k - 1][j - 1]);
                if (xnow > xprev) 
                    xarrSerial[k - 1][j - 1] = xnow;
                
                else 
                    xarrSerial[k - 1][j - 1] = -1 * xnow;
                
                xprev = xnow;
            
        
    

    t0 += omp_get_wtime();


    //parallel version
    xprev=0; xnow=0;
    double t = -omp_get_wtime();
    #pragma omp parallel for
    for (int k = 1; k <= nrows; k++) 
        std::vector<std::vector<double>> vecCol = vec3[k - 1];
        for (int j = 1; j <= vecCol.size(); j++) 
            if (j == 1) 
                xprev = 0;
                xarr[k - 1][j - 1] = xprev;
            
            else 
                //add vec3[k - 1][j - 2] and vec3[k - 1][j - 1]
                //then compare with xprev
                xnow = getSum(vec3[k - 1][j - 2], vec3[k - 1][j - 1]);
                if (xnow > xprev) 
                    xarr[k - 1][j - 1] = xnow;
                
                else 
                    xarr[k - 1][j - 1] = -1 * xnow;
                
                xprev = xnow;
            
        
    

    t += omp_get_wtime();

    std::cout << "xarrSerial\n";
    for (int k = 1; k <= 10; k++) 
        for (int j = 1; j <= 4; j++) 
            std::cout << xarrSerial[k - 1][j - 1] << ", ";
        
        std::cout << "\n";
    

    std::cout << "xarr\n";
    for (int k = 1; k <= 10; k++) 
        for (int j = 1; j <= 4; j++) 
            std::cout << xarr[k - 1][j - 1] << ", ";
        
        std::cout << "\n";
    
    std::cout << "\n";


    std::cout << "t0: " << t0 << std::endl;
    std::cout << "t: " << t << std::endl;

    return 0;


double getSum(std::vector<double>& xv, std::vector<double>& yv)

    double out=0.0;
    for (int i = 0; i < xv.size(); i++) 
        out = xv[i]*yv[i]+out;
    
    return out;

对于并行版本,我可以看到 #pragma omp parallel for 没有正确使用,因为每次计算都依赖于迭代中的前一个计算,如下所示:

xnow = getSum(vec3[k - 1][j - 2], vec3[k - 1][j - 1]);
if (xnow > xprev) 
    xarr[k - 1][j - 1] = xnow;

else 
    xarr[k - 1][j - 1] = -1 * xnow;

xprev = xnow;

我确认并行版本不正确。虽然rand()中使用了随机值,但举个例子,我得到的输出是

xarrSerial(串行版):

0 2047.63 -2040.89 -2018.98
0 2004.31 2031.86 2058.08
...

在并行版本中,xarr 返回

0 2047.63 -2040.89 -2018.98
0 -2004.31 2031.86 2058.08

我希望xarrxarrSerial 一样,但是-2004.31 中的负号显然是错误的

另外,并行版并没有明显比串行版快,串行版用了 2.78 秒,而并行版用了 2.54 秒,而我的电脑有 40 个线程

使用 OpenMP 进行并行化的正确方法是什么?或者由于if (xnow &gt; xprev),这不能与OpenMP并行化吗?

【问题讨论】:

【参考方案1】:

TL;DR: 保护变量xprevxnow使用firstprivate原因或使用局部变量

乍一看,由于迭代之间对xprevxnow 的迭代间依赖性,循环似乎天生是顺序的。但是如果我们仔细观察,实际上并没有迭代间的依赖关系,因为xprev 是在内循环中初始化的。但是,当您输入 #pragma omp parallel for 时,您会告诉编译器循环迭代之间没有依赖关系:代码编写者负责确保这样 OpenMP 可以生成正确的代码。 默认情况下,#pragma omp parallel for 之外的变量被视为在线程之间共享。您必须使用原因firstprivate(xprev, xnow) 明确告诉 OpenMP,不是。 编程中的最佳实践是减少变量的范围以提高代码的可读性以及能够更好地跟踪依赖关系。 所以,请在循环中移动xprevxnow

建议

std::vector&lt;std::vector&lt;double&gt;&gt; vecCol = vec3[k - 1]; 行大大减慢了代码速度,因为它涉及深拷贝。请使用参考删除慢副本:std::vector&lt;std::vector&lt;double&gt;&gt;&amp; vecCol = vec3[k - 1];

由于push_back,初始化很慢。请在之前使用reserve 或直接访问值,因为您已经知道向量的大小。

请不要将std::vector&lt;std::vector&lt;double&gt;&gt; 之类的类型用于多维数组。这效率不高,因为数据没有连续存储在内存中。更喜欢使用巨大的扁平数组/向量。

虽然getSum 由于可能依赖于out 而看起来是连续的,但事实并非如此。编译器可能无法向量化循环。您可以使用#pragma omp simd reduction(+:out) 来加快循环速度。

将所有这些放在一起,代码在我的 4 核机器上快了 11 倍,并且在顺序实现方面给出了正确的结果(从初始顺序代码的 1.781 秒到优化并行代码的 0.160 秒)。

【讨论】:

以上是关于如何在 omp 并行中使特定部件串行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在工作线程中重用主线程创建的OMP线程池?

尽管并行编译,Mex 文件仍串行执行

如何在kivy的gridlayout中使小部件跨越多个列/行

omp_get_max_threads() 在并行区域返回 1,但应该是 8

布局:如何在垂直布局中使一个小部件成为其余小部件的 3 倍

如何在Qt Creator中使两个小部件相互连接并相互调整大小[重复]