Openmp 递归任务

问题描述 投票:0回答:1

我是 Openmp 编程新手,我有一个关于递归任务并行性的问题

让我们考虑一下这个演示 C 代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <omp.h>

struct timeval t1, t2;

void recursive_task(int level)
{   
    //printf("%d\n", level);
    
    if (level == 0){
        usleep(1000);
        return;
    }
    else
    {
        recursive_task(level-1);

        #pragma omp task
        {
             recursive_task(level-1);
        }
        
        #pragma omp task
        {
             recursive_task(level-1);
        }
        
        #pragma omp taskwait
        
        recursive_task(level-1);
    }
}

int main()
{
    double time;

    gettimeofday(&t1, 0);

#pragma omp parallel
    {
#pragma omp single
        {
            recursive_task(7);
        }
    }

    gettimeofday(&t2, 0);

    time = (double)((t2.tv_sec - t1.tv_sec) * 1000000 + t2.tv_usec - t1.tv_usec) / 1000000;
    printf("%.4f\n", time);

    return 0;
}

每一级递归执行 4 次调用,其中只有第二次和第三次调用可以实际并行运行。

现在我尝试了这个不同的版本:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <omp.h>

struct timeval t1, t2;

void recursive_task(int level)
{   
    //printf("%d\n", level);
    
    if (level == 0){
        usleep(1000);
        return;
    }
    else
    {
        #pragma omp task if(0)
        {
            recursive_task(level-1);

            #pragma omp task
            {
                recursive_task(level-1);
            }
        
            recursive_task(level-1);

            #pragma omp taskwait
            
            recursive_task(level-1);
        }
    }
}

int main()
{
    double time;

    gettimeofday(&t1, 0);

#pragma omp parallel
    {
#pragma omp single
        {
            recursive_task(7);
        }
    }

    gettimeofday(&t2, 0);

    time = (double)((t2.tv_sec - t1.tv_sec) * 1000000 + t2.tv_usec - t1.tv_usec) / 1000000;
    printf("%.4f\n", time);

    return 0;
}

执行似乎也遵循正确的调用顺序(我认为这与 if(0) 子句有关)。

然而令我惊讶的是第二个比第一个更快。我使用 8 个线程执行,第一种方法需要 4.4 秒,第二种方法需要 3.4 秒!

使第二个代码更快的主要区别是什么?

c recursion openmp hpc
1个回答
0
投票

重点是影响

#pragma omp taskwait
等待当前任务的子任务完成,当有使用
#pragma omp task if(0)
创建的任务时,当前任务不一样。这意味着“不”拥有较晚的指令会导致“过度同步”,从而导致更高的执行时间(当然是由于并行性较低)。事实上,在这种情况下, #pragma omp taskwait 指令会等待更多任务(尤其是没有 if(0) 的真实任务。

2 个级别的示例

为了更好地理解发生了什么,我们可以分析该函数在级别 2 上到底做了什么(并将延迟增加到 10_000,以便我们可以在运行时看到任何差异)。这是扁平化代码:

void recursive_task_level2() { #pragma omp task if(0) { #pragma omp task if(0) { usleep(10000); #pragma omp task usleep(10000); usleep(10000); #pragma omp taskwait usleep(10000); } #pragma omp task // <----- [A] WAITED TASK { #pragma omp task if(0) { usleep(10000); #pragma omp task usleep(10000); usleep(10000); #pragma omp taskwait usleep(10000); } } #pragma omp task if(0) // <----- [B] IMPORTANT ONE { usleep(10000); #pragma omp task usleep(10000); usleep(10000); #pragma omp taskwait // <----- [C] usleep(10000); } #pragma omp taskwait #pragma omp task if(0) { usleep(10000); #pragma omp task usleep(10000); usleep(10000); #pragma omp taskwait usleep(10000); } } }

这个想法是:如果

[B]
 存在,那么 
[C]

不会等待

[A]
,因为它不再是子任务;但是,如果
[B]
不存在
,则
[C]
等待 [A],即等待 10 毫秒之前。
通过在有/没有 
[B]
以及有/没有所有其他

#pragma omp task if(0)

的情况下运行此代码来确认此效果。在这种情况下,唯一重要的指令是

[B]
,而注释
[B]
确实会增加执行时间 10 毫秒(92 毫秒 vs 102 毫秒)。这证明了假设是正确的。
随着级别的增加,由于过度同步,并行量会大大减少,并且额外的时间也会在关键路径上累积(当然是指数级的),从而导致巨大的执行开销。
    

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.