为什么OpenMP不允许!=运算符?


74

我正在尝试编译以下代码:

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

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

OpenMP的标准规定,对于parallel for构造它“仅仅”允许以下的运营商之一:<<=> >=

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


编辑:即使我做了for(i = 0; i != 100; i++),尽管我可以放“ <”或“ <=”。


9
+1哇,这是一个平凡而清晰的问题,实际上表明了自己的努力。
DrummerB

Answers:


67

我向OpenMP开发人员发送了有关此主题的电子邮件,得到的答案是:

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

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

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

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

对于像这样的循环:

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

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

对于像这样的循环:

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

如果n> = 0,则编译器可以生成代码,将跳闸计数(循环迭代计数)计算为floor((n + 1)/ 2);如果n <0则计算为0

对于像这样的循环:

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

编译器无法确定“ i”是否会达到“ n”。如果“ n”是一个奇数怎么办?

对于像这样的循环:

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

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

对于像这样的循环:

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

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

积分:OpenMP ARB


15
+1以联系开发人员,然后发布他们的回复。谢谢。
AndrewJacksonZA 2012年

18

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

OpenMP标准非常清楚,一旦遇到工作共享结构,就将对iteraton的数量进行预先计算,因此可能无法在循环体内修改循环计数器(OpenMP 3.1规范,第2.5.1节-循环构造):

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

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

工作共享循环具有编号为0,1,...,N-1的逻辑迭代,其中N是循环迭代的数量,并且逻辑编号表示如果执行了相关的循环,则将执行迭代的顺序。通过一个线程。该 schedule子句指定如何将关联循环的迭代划分为称为块的连续非空子集,以及如何在团队的线程之间分配这些块。每个线程在其隐式任务的上下文中执行分配的块。该CHUNK_SIZE使用循环构造中设为私有的任何变量的原始列表项来评估表达式。尚不确定该表达式的评估是否以什么顺序或次数出现任何副作用。在schedule循环构造的子句表达式中使用变量会导致在所有封闭构造中隐式引用该变量。

这些关系运算符限制的原理很简单-可以清楚地说明循环的方向,可以轻松计算迭代次数,并且可以提供C / C ++和Fortran中OpenMP工作共享指令的相似语义。 。另外,其他关系操作将需要仔细检查循环体,以了解循环的进行方式,这在许多情况下是无法接受的,并且会使实现变得麻烦。

OpenMP 3.0引入了显式task构造,该构造允许对未知迭代次数的循环进行并行化。但是有一个陷阱:任务会带来一些严重的开销,并且每个循环迭代一个任务只有在执行这些迭代需要花费一些时间的情况下才有意义。否则,开销将支配执行时间。


4

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


2

如果我要看声明

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语句中以及在循环本身中都会增加,从而导致无限循环的可能性(但不是保证)。

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

如果谓词是谓词,!=则循环的行为不再是明确定义的,并且在范围上可能是无限的,从而阻止了容易的并行细分。


2
您的示例符合OpenMP规范。您不得在循环体内修改循环计数器。单线程版本产生0 2 4 6 8了预期的结果,但是即使有两个线程,它也会产生以下输出:0 2 4 5 7 9
赫里斯托·伊利耶夫

如果您在常规顺序c程序中执行此操作,则示例也会失败。不过,编译器允许这样做。
dreamcrash 2012年

1
该示例并不是要失败(尽管可以随意更改范围),而是要演示编译器所遇到的困难:容易定义的行为<,很难定义的行为!=
理查德

@dreamcrash,他的示例作为连续的C代码如何失败?它是一个完全有效的串行C代码,可以按预期工作,但不是有效的OpenMP代码。
赫里斯托·伊利耶夫

我认为,这个答案最重要的部分是循环子句对程序员也有重要意义。 与@Richard状态<相比!=,它包含的信息要多于平均2倍,如果我看到循环中使用的表示法,则必须先通读循环主体,然后才能感觉到自己真正理解的是什么循环。
乔纳森·杜尔西

0

我认为,除了扩展现有功能以达到这一目标外,也许没有其他理由。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.