【问题标题】:Parallelizing Code inside a while-loop OpenMP在 while 循环 OpenMP 中并行化代码
【发布时间】:2021-10-18 18:10:54
【问题描述】:

情况:

我正在尝试编写偏微分方程的求解器。特别是它的并行实现(通过域分解)。该代码产生正确的结果。但是,我在使用 OpenMp 时遇到了一些问题,因为我必须在另一个 while 循环中执行多个 for 循环。在此之前,我没有任何使用 OpenMP 的经验,所以我有点不确定。我正在一个示例中测试代码,该示例应尽可能减少负载不平衡。在使用 4 个线程进行测试期间,我观察到所有 CPU 内核的使用几乎相同。

我有一个 intel i5-6500 4x3.2 CPU,我在 Windows 上使用 Clion 和 cygwin 2.11.1 (gcc)、cmake 3.19 和 C++20 以及编译器标志 -fopenmp 进行编译

程序的主要部分如下所示:

FunctionData data_array[N];
bool flag=true;

for (int i = 0; i < N; ++i){
    function1(data_array[i]);
}

while(flag){

    if(cancellation_criterium(data_array)){
        flag= false;    
    }

    for (int i = 0; i < N; ++i){
        function2(data_array[i]);
    }

    for (int i = 0; i < N; ++i){    
        function3(data_array[i]);
    }

}

每个子域的数据都在data_array 的相应条目中。应该没有数据竞争,每个函数都是一个void-函数,它修改了data_array[i],它是通过引用传递的。我检查了所有的结果,它们都是正确的。

cancellation_criterium 中,我想找到 FunctionData 的一些成员变量的最小值,因此我需要访问所有数组。 while循环执行了很多,我正在测试一个负载平衡问题应该很少的示例。 每个函数都可以并行执行,但它们必须一个接一个地执行(首先是函数 2 的所有迭代,然后是函数 3)。我确保没有数据竞争,每个功能只需要

由于我是 OpenMP 的新手,我的第一个想法是在每个 for 循环之前添加 #pragma omp parallel for。然而,这极大地(2-3 倍)增加了程序的运行时间。在与一些朋友交谈并在网上做了一些研究后,我得出结论,在 while 循环中不断创建和加入线程可能是造成这种情况的原因。

我的第二种方法是创建一个环绕的#omp parallel 区域。结果代码是:

FunctionData data_array[N];
bool flag=true;
#pragma omp parallel shared(data_array, flag)
{
#pragma omp for
    for (int i = 0; i < N; ++i){
        function1(data_array[i]);
    }
#pragma omp barrier    
    while(flag){
    #pragma omp master
        {
            if(cancellation_criterium(data_array)){
                flag= false;    
            }
        }
    #pragma omp barrier
    #pragma omp for
        for (int i = 0; i < N; ++i){
            function2(data_array[i]);
        }
    #pragma omp barrier
    #pragma omp for
        for (int i = 0; i < N; ++i){    
            function3(data_array[i]);
        }
    #pragma omp barrier
    }

}

这也遭受了与第一次尝试类似的性能损失。我还尝试使用函数 2 和 3 在 for 循环周围设置平行区域,但无济于事。

在我最后一次尝试中,我使用了并行区域,但在其中使用了#pragma omp for-loops。尽管我认为这不应该工作,因为我认为我创建了嵌套的并行区域,我一直观察到性能略有提高。

代码如下:

FunctionData data_array[N];
bool flag=true;
#pragma omp parallel shared(data_array, flag)
{
    #pragma omp master
    #pragma omp parallel for
    for (int i = 0; i < N; ++i){
        function1(data_array[i]);
    }
    #pragma omp master    
    while(flag){
    
        if(cancellation_criterium(data_array)){
            flag= false;    
        }
        #pragma omp parallel for
        for (int i = 0; i < N; ++i){
            function2(data_array[i]);
        }
        #pragma omp parallel for
        for (int i = 0; i < N; ++i){    
            function3(data_array[i]);
        }
    
    }

}

问题

1. 尝试 3 的表现比尝试 1 好得多,或者你有什么想法为什么应该这样?我无法理解最后一次尝试如何创建和加入比第一个版本更少的线程,即使它可以更快。

2. 在第二次尝试中,当代码到达while(flag) 时,并行区域中的每个线程是否执行为并行区域中的每个线程创建的这个while 循环?我怀疑这可能会发生,但程序的结果是正确的。

3. 有没有更聪明的方法可以用 OpenMP 解决这个问题?

原始代码的长度相当长,但如果你想看看,我很乐意给你github页面的链接。

---edit--在第一次尝试时添加了#pragma omp parallel for,N是2或4。

【问题讨论】:

  • N 有多大,function1..3 需要多少时间?您在每个 for 循环之前都写了 #pragma omp parallel,这是缺少 for 的错字吗?如果没有,首先尝试在每个 for 循环之前使用 #pragma omp parallel for
  • 你说你认为负载应该是平衡的,但测试不同的调度永远不会有坏处。用schedule(dynamic) 和/或schedule(guided) 试试看是否有任何改进。
  • @Laci 是的,这是一个错字,谢谢。 N 是 2 或 4,非常小
  • 好吧,那就是你的问题。如果您只有 2-4 次循环迭代,您希望如何在线程之间分配迭代?
  • 我做了一些测量,发现使用#pragma omp parallel 时,function2 的循环时间要长 3-4 倍,而其他循环的运行时间会减少。这也占用了总运行时间的 95% 以上。

标签: c++ multithreading openmp


【解决方案1】:

我只是回答问题3.:我认为并行化代码的最佳方式是使用任务:

#pragma omp parallel 
#pragma omp single nowait
{
    bool flag=true;

    for (int i = 0; i < N; ++i){
        #pragma omp task
        function1(data_array[i]);
    }

    while(flag){
        #pragma omp taskwait
        if(cancellation_criterium(data_array)){
            flag= false;    
        }

        for (int i = 0; i < N; ++i){
            #pragma omp task
            {
              function2(data_array[i]);
              function3(data_array[i]);
            }
        }

    }
}

【讨论】:

  • @weiskohlmoe 你试过基于任务的并行化吗?
  • 感谢您的建议,正如我在回答中指出的那样,这不是真正的问题。但是我现在有一个带有任务的版本,它(除了功能 2)工作得非常好:)
【解决方案2】:

我发现了我的问题,不幸的是它与原始问题无关。 FunctionData 的一部分是std::vector,其中function2 经常使用推回。我发现在并行区域分配内存非常昂贵。 见这里:

C++ dynamic memory allocation is slower in OpenMP, even for non-parallel sections of code

【讨论】:

  • 或许您应该考虑使用不同的算法(或数值库)。
  • 我只是在所有东西上都使用了 vector::reserve,它已经比顺序版本快了约 1.7 倍(N=2 所以它相当不错)。感谢您的建议!
猜你喜欢
  • 2016-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-08
  • 1970-01-01
相关资源
最近更新 更多