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);
}
答案 0 :(得分:12)
不同之处在于,在第一个块中,您不实际创建任何任务,因为块本身不是在活动并行区域内嵌套(在语法上也不是词法上)。在第二个块中,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
区域处于活动状态,因为4
子句将线程数固定为num_threads(4)
。
OpenMP指令的这种不同行为是一种设计特性。主要思想是能够编写可以作为串行和并行执行的代码。
task
中foo()
构造的存在仍会进行一些代码转换,例如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 -> foo -> bar
,而是main -> foo -> OMP_make_task -> foo_omp_fn_1 -> 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个iteratons。再一次,使用了一些代码转换魔术来实现这一点,并且串行循环的运行速度比#pragma omp for
中没有foo()
的情况要慢。
这里的教训是永远不要在不需要的地方添加OpenMP结构。