首页 > 解决方案 > 关于 OpenMP 部分和关键部分的问题

问题描述

我正在尝试制作一个快速的并行循环。在循环的每次迭代中,我构建了一个成本高昂的数组,因此我希望它分布在多个线程上。构建数组后,我用它来更新矩阵。在这里它变得很棘手,因为矩阵对所有线程都是通用的,所以只有 1 个线程可以一次修改矩阵的一部分,但是当我处理矩阵时,事实证明我也可以分配这项工作,因为我可以处理不同的部分同时矩阵。

这是我目前正在做的事情:

#pragma omp parallel for
for (i = 0; i < n; ++i)
{
  ... build array bi ...
  #pragma omp critical
  {
    update_matrix(A, bi)
  }
}

...

subroutine update_matrix(A, b)
{
  printf("id0 = %d\n", omp_get_thread_num());
  #pragma omp parallel sections
  {
    #pragma omp section
    {
      printf("id1 = %d\n", omp_get_thread_num());
      modify columns 1 to j of A using b
    }

    #pragma omp section
    {
      printf("id2 = %d\n", omp_get_thread_num());
      modify columns j+1 to k of A using b
    }
  }
}

问题是 update_matrix() 例程的两个不同部分没有被并行化。我得到的输出如下所示:

id0 = 19
id1 = 0
id2 = 0
id0 = 5
id1 = 0
id2 = 0
...

所以这两个部分由同一个线程(0)执行。我尝试在主循环中删除#pragma omp critical,但它给出了相同的结果。有谁知道我做错了什么?

标签: parallel-processingopenmp

解决方案


#pragma omp parallel sections不应该在那里工作,因为您已经在该#pragma omp prallel for子句分发的代码的并行部分中。除非您使用 启用嵌套并行化,否则omp_set_nested(1);parallel sections子句将被忽略。

请注意,它不一定是有效的,因为产生新线程会产生开销成本,如果该update_matrix部分不是太 CPU 密集型,则可能不值得。

你有几个选择:

  • 把它给忘了。如果循环的非关键部分确实需要进行大多数计算,并且您已经拥有与 CPU 一样多的线程,那么为简单的操作生成额外的线程将没有好处。只需删除parallel sections子例程中的子句。

  • 尝试启用嵌套omp_set_nested(1);

  • 另一种选择是以双倍同步开销为代价的,将使用命名临界区。ONE_TO_J 部分可能只有一个线程,criticalJ_TO_K 部分可能只有一个线程,critical因此基本上最多两个线程可以并行更新矩阵。这在同步开销方面是昂贵的。

    #pragma omp parallel for
    for (i = 0; i < n; ++i)
    {
      ... build array bi ...
      update_matrix(A, bi); // not critical
    }
    
    ...
    
    subroutine update_matrix(A, b)
    {
      printf("id0 = %d\n", omp_get_thread_num());
        #pragma omp critical(ONE_TO_J)
        {
          printf("id1 = %d\n", omp_get_thread_num());
          modify columns 1 to j of A using b
        }
    
        #pragma omp critical(J_TO_K)
        {
          printf("id2 = %d\n", omp_get_thread_num());
          modify columns j+1 to k of A using b
        }
    }
    
  • 如果合适的话,或者使用原子操作来编辑矩阵。

    #pragma omp parallel for
    for (i = 0; i < n; ++i)
    {
      ... build array bi ...
      update_matrix(A, bi); // not critical
    }
    
    ...
    
    subroutine update_matrix(A, b)
    {
        float tmp;
        printf("id0 = %d\n", omp_get_thread_num());
        for (int row=0; row<max_row;row++)
            for (int column=0;column<k;column++){
                float(tmp)=some_function(b,row,column);
                #pragma omp atomic
                A[column][row]+=tmp;
                }
    
    }
    

    顺便说一句,数据在 C 中以行主要顺序存储,因此您应该逐行而不是逐列更新矩阵。这将防止错误共享并提高算法的内存访问性能。


推荐阅读