【问题标题】:OpenMP task with and without parallel具有和不具有并行性的 OpenMP 任务
【发布时间】:2012-11-17 13:39:49
【问题描述】:
Node *head = &node1;
while (head)
{
    #pragma omp task 
        cout<<head->value<<endl;
    head = head->next;
}

#pragma omp parallel
{
    #pragma omp single
    {
        Node *head = &node1;
        while (head)
        {
            #pragma omp task 
                cout<<head->value<<endl;
            head = head->next;
        }
    }
}

在第一个块中,我只是创建了没有并行指令的任务,而在第二个块中,我使用了并行指令和单指令,这是我在论文中看到的常见方式。 我想知道他们之间有什么区别?顺便说一句,我知道这些指令的基本含义。

我评论中的代码:

void traverse(node *root)
{
    if (root->left) 
    {
        #pragma omp task 
        traverse(root->left);
    }
    if (root->right) 
    {
        #pragma omp task 
        traverse(root->right);
    }
    process(root);
}

【问题讨论】:

    标签: task openmp


    【解决方案1】:

    不同之处在于,在第一个块中,您没有真正创建任何任务,因为块本身没有嵌套(无论是在语法上还是在词法上)在一个活动的并行区域内。在第二个块中,task 构造在语法上嵌套在parallel 区域内,如果该区域恰好在运行时处于活动状态,则会将显式任务排队(活动并行区域是与一个以上的团队一起执行的区域)线)。词法嵌套不太明显。请注意以下示例:

    void foo(void)
    {
       int i;
    
       for (i = 0; i < 10; i++)
          #pragma omp task
          bar();
    }
    
    int main(void)
    {
       foo();
    
       #pragma omp parallel num_threads(4)
       {
          #pragma omp single
          foo();
       }
    
       return 0;
    }
    

    foo() 的第一次调用发生在任何并行区域之外。因此task 指令(几乎)什么都不做,所有对bar() 的调用都是连续发生的。对foo() 的第二次调用来自并行区域内部,因此将在foo() 内部生成新任务。 parallel 区域处于活动状态,因为 num_threads(4) 子句将线程数固定为 4

    OpenMP 指令的这种不同行为是一项设计功能。主要思想是能够编写可以串行和并行执行的代码。

    foo() 中仍然存在 task 结构会进行一些代码转换,例如foo() 被转换为:

    void foo_omp_fn_1(void *omp_data)
    {
       bar();
    }
    
    void foo(void)
    {
       int i;
    
       for (i = 0; i < 10; i++)
          OMP_make_task(foo_omp_fn_1, NULL);
    }
    

    这里的OMP_make_task() 是 OpenMP 支持库中的一个假设(未公开提供)函数,它将对该函数的调用排队,作为其第一个参数提供。如果OMP_make_task() 检测到它在活动并行区域之外工作,它会简单地调用foo_omp_fn_1()。这增加了在串行情况下对bar() 的调用的一些开销。电话不是main -&gt; foo -&gt; bar,而是main -&gt; foo -&gt; OMP_make_task -&gt; foo_omp_fn_1 -&gt; bar。这意味着串行代码执行速度较慢。

    工作共享指令更清楚地说明了这一点:

    void foo(void)
    {
       int i;
    
       #pragma omp for
       for (i = 0; i < 12; i++)
          bar();
    }
    
    int main(void)
    {
       foo();
    
       #pragma omp parallel num_threads(4)
       {
          foo();
       }
    
       return 0;
    }
    

    第一次调用foo() 将串行运行循环。第二次调用将在 4 个线程中分配 12 次迭代,即每个线程只会执行 3 次迭代。再一次,使用一些代码转换魔法来实现这一点,串行循环的运行速度会比foo() 中不存在#pragma omp for 时要慢。

    这里的教训是永远不要在不需要的地方添加 OpenMP 结构。

    【讨论】:

    • 好像是我在task的使用上搞错了。之所以出现这个问题,是因为我看到了一个只有“任务”才能递归遍历树的代码,正如我在问题中添加的那样。我想应该有“并行”和“单一”包围它被调用的遍历函数。非常感谢您的真诚回答。
    • @AnnieKim,是的,如果从活动的parallel 区域内调用,则问题中显示的traverse() 函数将并行遍历树,否则依次遍历。这就是 OpenMP 的美妙之处 :)(尽管增加了开销)
    猜你喜欢
    • 1970-01-01
    • 2017-04-29
    • 1970-01-01
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多