为啥 OpenMP 不允许使用 != 运算符?

Posted

技术标签:

【中文标题】为啥 OpenMP 不允许使用 != 运算符?【英文标题】:Why is the != operator not allowed with OpenMP?为什么 OpenMP 不允许使用 != 运算符? 【发布时间】:2012-10-30 01:04:31 【问题描述】:

我试图编译以下代码:

#pragma omp parallel shared (j)

   #pragma omp for schedule(dynamic)
   for(i = 0; i != j; i++)
   
      // do something
   

但我收到以下错误:错误:无效的控制谓词

OpenMP standard 声明对于 parallel for 构造函数,它“仅”允许以下运算符之一:<<=>>=

我不明白不允许i != j 的理由。对于static schedule,我可以理解,因为编译器需要预先计算分配给每个线程的迭代次数。但是我不明白为什么在这种情况下会出现这种限制。有什么线索吗?


编辑:即使我写了for(i = 0; i != 100; i++),虽然我可以只写“

【问题讨论】:

【参考方案1】:

.

我就这个主题向 OpenMP 开发者发送了一封电子邮件,我得到了答复:

对于有符号整数,环绕行为是未定义的。如果我们允许!=程序员可能会得到意外的tripcount。问题是编译器是否可以生成代码来计算循环的行程计数。

对于一个简单的循环,例如:

for( i = 0; i < n; ++i )

编译器可以确定存在“n”次迭代,如果 n>=0,以及零次迭代如果 n 。

对于像这样的循环:

for( i = 0; i != n; ++i ) 

同样,编译器应该能够确定存在“n”次迭代,如果 n >= 0; 如果 n ,我们不知道它有多少次迭代。

对于像这样的循环:

for( i = 0; i < n; i += 2 )

编译器可以生成代码来计算行程计数(循环迭代计数)为 floor((n+1)/2) 如果 n >= 0 和 0 如果 n .

对于像这样的循环:

for( i = 0; i != n; i += 2 )

编译器无法确定“i”是否会命中“n”。如果 'n' 是奇数呢?

对于像这样的循环:

for( i = 0; i < n; i += k )

编译器可以生成代码来计算行程计数,如果 n >= 0 和 0 floor((n+k-1)/k),如果 n strong>,因为编译器知道循环必须向上计数;在这种情况下,如果 k ,则不是合法的 OpenMP 程序。

对于像这样的循环:

for( i = 0; i != n; i += k )

编译器甚至不知道我是向上还是向下计数。它不知道“i”是否会命中“n”。这可能是一个无限循环。

致谢:OpenMP ARB

【讨论】:

【参考方案2】:

答案很简单。 OpenMP 不允许提前终止一组线程。 使用 == 或 !=,OpenMP 无法确定循环何时停止。 1. 一个或多个线程可能达到终止条件,这可能不是唯一的。 2. OpenMP 无法关闭其他可能永远不会检测到该条件的线程。

【讨论】:

【参考方案3】:

与它的外观相反,schedule(dynamic) 不适用于动态数量的元素。相反,将迭代块分配给线程是动态的。对于静态调度,此分配是在工作共享构造的开始时预先计算的。通过动态调度,迭代块按照先到先得的原则分配给线程。

OpenMP 标准非常清楚,一旦遇到工作共享构造,就会预先计算迭代的数量,因此循环计数器可能不会在循环体内被修改(OpenMP 3.1 规范,§2.5.1 - 循环构造) :

每个关联循环的迭代计数在进入最外层之前计算 环形。如果任何关联循环的执行更改了用于计算任何值的任何值 的迭代计数,则行为未指定。

用于计算迭代次数的整数类型(或种类,对于 Fortran) 折叠循环是实现定义的。

工作共享循环具有编号为 0,1,...,N-1 的逻辑迭代,其中 N 是 循环迭代,逻辑编号表示迭代的顺序 如果关联的循环由单个线程执行,则将执行。这 schedule 子句指定关联循环的迭代如何划分为 连续的非空子集,称为块,以及这些块是如何分布的 在团队的线程之间。每个线程在 它的隐含任务。 chunk_size 表达式使用在循环构造中设为私有的任何变量的原始列表项进行评估。未指定是否以什么顺序或多少次出现此表达式的求值的任何副作用。在循环构造的schedule 子句表达式中使用变量会导致在所有封闭构造中隐式引用该变量。

这些关系运算符限制背后的基本原理非常简单——它清楚地表明了循环的方向,它可以轻松计算迭代次数,并且它提供了与 C/OpenMP 工作共享指令类似的语义C++ 和 Fortran。此外,其他关系操作需要仔细检查循环体,以了解循环的运行方式,这在许多情况下是不可接受的,并且会使实现变得繁琐。

OpenMP 3.0 引入了显式的task 构造,它允许以未知的迭代次数并行化循环。但是有一个问题:任务引入了一些严重的开销,并且每个循环迭代的一个任务只有在这些迭代需要相当长的时间才能执行时才有意义。否则开销将支配执行时间。

【讨论】:

【参考方案4】:

如果我看到声明

for(i = 0; i != j; i++)

用来代替语句

for(i = 0; i < j; i++)

我会想为什么程序员会做出这样的选择,更不用说它可能意味着同样的事情。可能 OpenMP 正在做出艰难的句法选择,以强制实现一定的代码清晰度。

这里的代码对!= 的使用提出了挑战,可能有助于解释为什么不允许这样做。

#include <cstdio>

int main()
    int j=10;
   #pragma omp parallel for
   for(int i = 0; i < j; i++)
    printf("%d\n",i++);
   

请注意,ifor 语句以及循环本身中都会增加,从而导致无限循环的可能性(但不是保证)。

如果谓词是&lt;,那么循环的行为仍然可以在并行上下文中得到很好的定义,而编译器不必在循环内检查对i 的更改并确定这些更改将如何影响循环的边界。

如果谓词是!=,则循环的行为不再是明确定义的,它的范围可能是无限的,从而阻碍了简单的并行细分。

【讨论】:

您的示例符合 OpenMP 规范。您不得修改循环体内的循环计数器。单线程版本按预期产生0 2 4 6 8,但即使有两个线程,它也会产生以下输出:0 2 4 5 7 9 如果您在正常的顺序 c 程序中这样做,您的示例也会失败。不过,编译器允许这样做。 这个例子并不意味着失败(尽管你可以随意改变它的边界),它是为了证明编译器有一个困难:为&lt;定义行为很容易,为!=定义行为是相当困难的。 @dreamcrash,他的示例如何作为顺序 C 代码失败?它是一个完全有效的串行 C 代码,可以按预期工作,但不是有效的 OpenMP 代码。 我认为这个答案中最重要的部分是循环子句对程序员也有一定的意义。 &lt; 包含的信息比!= 多,平均是两倍,正如@Richard 所说,如果我看到循环中使用了该符号,那么我必须通读循环体,然后才能感觉到我了解循环中实际发生的变化。【参考方案5】:

我认为除了扩展现有功能之外可能没有其他充分的理由来达到这一点。

IIRC 最初这些必须是静态的,以便它可以在编译时确定如何生成循环代码......它可能只是一个后遗症。

【讨论】:

以上是关于为啥 OpenMP 不允许使用 != 运算符?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C 在使用条件运算符时不允许连接字符串?

不了解使用最大运算 OpenMP 程序并行减少的输出?

为啥 C# 6.0 在使用 Null 传播运算符时不允许设置非 null 可空结构的属性?

为啥 C# 6.0 在使用 Null 传播运算符时不允许设置非 null 可空结构的属性?

为啥在java中不允许没有括号的赋值和布尔运算符

为啥条件运算符不能正确地允许使用“null”来分配给可为空的类型? [复制]