openMP C++ 简单并行区域 - 输出不一致

Posted

技术标签:

【中文标题】openMP C++ 简单并行区域 - 输出不一致【英文标题】:openMP C++ simple parallel region - inconsistent output 【发布时间】:2016-06-05 00:13:39 【问题描述】:

如上所述,我一直在尝试制作一个简单的并行循环,但是对于不同数量的线程,它的行为不一致。这是我的代码(可测试!)

#include <iostream>
#include <stdio.h>
#include <vector>
#include <utility>
#include <string>

using namespace std;

int row = 5, col = 5;
int token = 1;

int ar[20][20] = 0;

int main (void)


    unsigned short j_end = 1, k = 1;
    unsigned short mask;

    for (unsigned short i=1; i<=(row + col -1); i++)
    

        #pragma omp parallel default(none) shared(ar) firstprivate(k, row, col, i, j_end, token) private(mask)
        
            if(i > row) 
                mask = row;
            
            else 
                mask = i;
            

            #pragma omp for schedule(static, 2)
            for(unsigned short j=k; j<=j_end; j++)
            
                ar[mask][j] = token;
                if(mask > 1) 
                    #pragma omp critical
                    
                        mask--;
                    
                       
             //inner loop - barrier

        //end parallel

        token++;
        if(j_end == col)                           
            k++;
            j_end = col;
        
        else 
            j_end++;
        

     // outer loop

    // print the array
    for (int i = 0; i < row + 2; i++)
    
       for (int j = 0; j < col + 2; j++)
       
           cout << ar[i][j] << " ";
       

        cout << endl;
    

    return 0;
 // main

我相信大部分代码都是不言自明的,但总而言之,我有 2 个循环,内部循环遍历方阵 ar[row][col] 的反对角线,(row & col 变量可用于更改ar) 的总大小。

视觉辅助:5x5 ar 的所需输出(串行版本) (注意:OMP_NUM_THREADS=1 也会发生这种情况。)

但是当OMP_NUM_THREADS=2OMP_NUM_THREADS=4 输出看起来像这样:

串行(和 1 个线程)代码是一致的,所以我认为实现没有问题。此外,鉴于串行代码的输出,内循环中不应存在任何依赖关系。

我也试过了:

矢量化 内部循环的 threadpivate 计数器

但到目前为止似乎没有任何效果......

我的方法有问题,还是我错过了导致这种行为的 API 方面的问题?

提前感谢您的宝贵时间。

【问题讨论】:

没有理由将firstprivate 用于只读变量。一个好的经验法则是始终将 shared 用于只读变量。 @NoseKnowsAll 好吧,我认为它可能会稍微提高性能,因为价格(在内存中)很小,相对于所有线程需要不断访问同一个变量的时间 - 因为它们确实访问它总共数百万次(对于大型矩阵) 良好的思考过程。但是,仅仅访问内存不会导致线程争用。仅当正在访问的内存已被任何其他线程更改时才会发生这种情况。因此,我建议将shared 用于只读变量。 @NoseKnowsAll 听起来不错,但遗憾的是,这并不能解决我的问题.. 【参考方案1】:

分析算法

正如您所指出的,算法本身在内部 外部循环中没有依赖关系。展示这一点的一种简单方法是将并行度“向上”移动到外循环,以便您可以同时遍历所有不同的反对角线。

目前,您编写的算法的主要问题是它在内部和外部循环中都呈现为串行算法。如果要跨内部循环并行化,则需要特别处理mask。如果要跨外循环并行化,则需要特别处理j_endtokenk。通过“特别处理”,我的意思是它们需要独立于其他线程进行计算。如果您尝试将关键区域添加到您的代码中,您将首先扼杀添加 OpenMP 的所有性能优势。

解决问题

在下面的代码中,我在外循环上进行了并行化。 i 对应于您所说的 token。也就是说,它既是要添加到反向对角线的值,也是该对角线的假定起始长度。请注意,为了正确并行化,lengthstartRowstartCol 必须独立于其他迭代计算为 i 的函数。

最后请注意,一旦以这种方式重新编写算法,实际的 OpenMP pragma 就非常简单。默认情况下,假定每个变量都是共享的,因为它们都是只读的。唯一的例外是ar,我们小心不要覆盖另一个线程的数组值。所有必须私有的变量仅在并行循环内创建,因此根据定义是线程私有的。最后,我将调度更改为动态,以展示该算法表现出负载不平衡。在您的示例中,如果您有 9 个线程(最坏的情况),您可以看到分配给 i=5 的线程必须比分配给 i=1i=9 的线程做更多的工作。

示例代码

#include <iostream>  
#include <omp.h>

int row = 5;
int col = 5;

#define MAXSIZE 20
int ar[MAXSIZE][MAXSIZE] = 0;

int main(void)


    // What an easy pragma!

    #pragma omp parallel for default(shared) schedule(dynamic)
    for (unsigned short i = 1; i < (row + col); i++)
    
        // Calculates the length of the current diagonal to consider
        // INDEPENDENTLY from other i iterations!
        unsigned short length = i;
        if (i > row) 
            length -= (i-row);
        
        if (i > col) 
            length -= (i-col);
        

        // Calculates the starting coordinate to start at
        // INDEPENDENTLY from other i iterations!
        unsigned short startRow = i;
        unsigned short startCol = 1;
        if (startRow > row) 
            startCol += (startRow-row);
            startRow = row;
        

        for(unsigned short offset = 0; offset < length; offset++) 
            ar[startRow-offset][startCol+offset] = i;    
        

     // outer loop

    // print the array
    for (int i = 0; i <= row; i++)
    
       for (int j = 0; j <= col; j++)
       
           std::cout << ar[i][j] << " ";
       

        std::cout << std::endl;
    

    return 0;
 // main

最后的积分

我想留下最后几点。

如果您只是在小型阵列 (row,col &lt; 1e6) 上添加并行性,您很可能不会从 OpenMP 中获得任何好处。在一个小数组上,算法本身需要几微秒,而设置线程可能需要几毫秒......与原始串行代码相比,这大大减慢了执行时间! 虽然我确实重写了此算法并更改了变量名称,但我尽量保持您的实施精神。因此,反对角扫描和嵌套循环模式仍然存在。 不过,有一种更好的方法可以并行化此算法以避免负载平衡。相反,如果您给每个线程一行并让它迭代其标记值(即行/线程 2 放置数字 2->6),那么每个线程将在完全相同数量的数字上工作,您可以将 pragma 更改为schedule(static)。 正如我在上面的 cmets 中提到的,当您的意思是 shared 时,不要使用 firstprivate。一个好的经验法则是所有只读变量都应该是shared。 假设在 1 个线程上运行并行代码时获得正确的输出意味着实现是正确的,这是错误的。事实上,除非灾难性地使用 OpenMP,否则仅使用 1 个线程就不太可能得到错误的输出。多线程测试表明您之前的实现不正确。

希望这会有所帮助。

编辑:对于 5x5 矩阵,我得到的输出是 the same as yours。

【讨论】:

感谢您的尝试,尽管您最初提供的 sn-p 无法正常工作。我尝试执行此操作,但出现编译器错误。另外,某处不应该有#pragma omp 吗?否则for循环不会被并行化吧? @Mechanic 确实有一个错字。我省略了#include &lt;omp.h&gt;pragma omp for。你到底得到了什么编译器错误? 这是生成的错误:'schedule' is not valid for '#pragma omp parallel' #pragma omp parallel default(shared) schedule(dynamic) 尽管现在你已修复它。 另外,你能附上输出的截图吗?因为当我运行它时(使用新的修复程序)与我上面描述的有很大不同 当我给它row=5,col=5时,我会得到你的确切image posted above...

以上是关于openMP C++ 简单并行区域 - 输出不一致的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 OpenMP 通过 C++ std::list 并行化 for 循环?

C++ OpenMP 未并行运行

C++:OpenMP 并行循环内存泄漏

使用 openmp 时运行的线程数不一致

用于区域线程关联的 OpenMP 并行

并行任务中的 C++ OpenMP 变量可见性