并行化具有许多退出点的算法?

时间:2012-03-31 01:33:26

标签: windows multithreading fortran parallel-processing openmp

我面临并行化算法,该算法在其串行实现中检查更大的三维数组中的多维数组位置的六个面。 (也就是说,选择一个数组元素,然后在x,y和z之间的元素'n'元素周围定义一个立方体或长方体,以数组的边界为界。

每个工作单元看起来像这样(Fortran伪代码;串行算法在Fortran中):

do n1=nlo,nhi
  do o1=olo,ohi          
    if (somecondition(n1,o1) .eq. .TRUE.) then
       retval =.TRUE.
       RETURN
    endif    
  end do 
end do 

或C伪代码:

for (n1=nlo,n1<=nhi,n++) {
  for (o1=olo,o1<=ohi,o++) {
    if(somecondition(n1,o1)!=0) {
      return (bool)true;
    }
  }
}

总算法中有六个这样的工作单位,其中'lo'和'hi'值通常介于10到300之间。

我认为最好的方法是安排六个或更多执行线程,如果没有那么多CPU内核,则循环,理想情况是并行执行循环,其目标与串行算法相同:somecondition()变为True,所有线程之间的执行必须立即停止,并在共享位置设置值True

Windows编译器中有哪些技术可以方便地并行化这样的任务?显然,我需要一个等待信号量或工作线程完成的主线程,因此需要嵌套和信令,但我对OpenMP的经验是在这一点上的介绍。

OpenMP中是否有消息传递机制?

编辑:如果“nlo”和“nhi”或“olo”和“ohi”之间的最大差异为8到10,则表示此嵌套循环不超过64到100次迭代,并且不超过384六个工作单元共600次迭代。基于此,是否值得并行化?

5 个答案:

答案 0 :(得分:3)

将循环并行化在数组元素上并保持此算法是串行的,多个线程在不同的数组元素上运行算法会更好吗?我从你的评论中想到这一点“时间消耗来自于数组中的每个元素都必须像这样测试的事实。这些数组通常有四百万到二千万个元素。”实现数组元素的并行化的设计在线程数方面也是灵活的。除非有理由必须按某种顺序检查数组元素吗?

似乎您向我们展示的部分执行时间并不长,因此通过使其并行可能并不容易花费更少的时钟时间...多线程总会有一些开销,如果没有太多时间可以获得,并行代码可能不会更快。

答案 1 :(得分:1)

我相信你可以使用OpenMP 3中引入的任务构造做你想做的事情; Intel Fortran支持OpenMP中的任务。我不经常使用任务,所以我不会给你任何不可思议的伪代码。

答案 2 :(得分:1)

你已经提到了在任何线程找到结束条件后立即停止所有线程的明显方法:让每个线程检查一些共享变量,该变量给出结束条件的状态,从而确定是否突破循环。显然这是一个开销,所以如果你决定采取这种方法,我会建议一些事情:

  1. 使用atomics检查结束条件,这可以避免昂贵的内存刷新,因为只是刷新了有问题的变量。转到OpenMP 3.1,支持一些新的原子操作。

  2. 不经常检查,可能每次外部迭代一次。您应该只是并行化大型案例以克服多线程的开销。

  3. 这个是可选的,但您可以尝试添加编译器提示,例如如果您希望大多数时候某个条件都是假的,编译器会相应地优化代码。

  4. 另一种(有点脏)方法是为每个线程的循环范围使用共享变量,也可以使用共享数组,其中索引n用于线程n。当一个线程找到结束条件时,它会更改所有其他线程的循环范围,以便它们停止。您需要适当的内存同步。基本上,开销已经从检查虚拟变量转移到同步/检查循环条件。经常这样做可能不太好,所以也许使用共享的外部循环变量和私有的内部循环变量。

    另一方面,这让我想起了经典的轮询与中断问题。不幸的是,我不认为OpenMP支持中断,你可以向每个线程发送一些kill信号。

    有一些黑客解决方法,比如使用子进程来完成这项并行工作并调用操作系统调度程序来模拟中断,但是这样做是非常棘手的,并且会使代码极不可移植。

    回复评论时更新:

    尝试这样的事情:

    char shared_var = 0;
    #pragma omp parallel
    {
      //you should have some method for setting loop ranges for each thread
      for (n1=nlo; n1<=nhi; n1++) {
        for (o1=olo; o1<=ohi; o1++) {
          if (somecondition(n1,o1)!=0) {
            #pragma omp atomic write
            shared_var = 1;  //done marker, this will also trigger the other break below
            break;           //could instead use goto to break out of both loops in 1 go
          }
        }
        #pragma omp atomic read
        private_var = shared_var;
        if (private_var!=0) break;
      }
    }
    

答案 3 :(得分:1)

一种可能性是使用OpenMP在6个循环上并行化 - 声明logical :: array(6),允许每个循环运行完成,然后retval = any(array)。然后,您可以检查此值并返回并行化循环外部。如果执行此操作,请在并行do语句中添加schedule(dynamic)。或者,有一个单独的!$omp parallel,然后在{6}循环的每一个周围放置!$omp do schedule(dynamic) ... !$omp end do nowait

或者,您可以遵循@ M.S.B的好建议。并在整个数组上并行化最外层循环。这里的问题是你不能在并行循环中有RETURN - 所以标记第二个最外层循环(并行部分中最大的循环),以及EXIT循环 - 如同

retval = .FALSE.
!$omp parallel do default(private) shared(BIGARRAY,retval) schedule(dynamic,1)
do k=1,NN
   if(.not. retval) then
      outer2: do j=1,NN
         do i=1,NN
            ! --- your loop #1
            do n1=nlo,nhi
               do o1=olo,ohi
                  if (somecondition(BIGARRAY(i,j,k),n1,o1)) then
                     retval =.TRUE.
                     exit outer2
                  endif
               end do
            end do
            ! --- your loops #2 ... #6 go here
         end do
      end do outer2
   end if
end do 
!$omp end parallel do

[编辑:if语句假定你需要找出大数组中是否至少有一个这样的元素。如果你需要为每个元素计算条件,你可以类似地添加一个虚拟循环退出或goto,跳过该元素的其余处理。再次,使用时间表(动态)或时间表(指导)。]

作为一个单独的点,您可能还想检查是否最好通过更大的步骤(取决于浮点大小)来完成最内层循环,在每次迭代时计算逻辑的向量,然后聚合结果,例如。像if(count(somecondition(x(o1:o1+step,n1,k)))>0)一样的smth;在这种情况下,编译器可以向量化somecondition

答案 4 :(得分:0)

一种合适的并行方法可能是让每个工作人员检查整个问题的一部分,就像在串行情况下一样,并使用本地(非共享)变量作为结果(retval)。最后,将这些局部变量上的所有工人减少为共享的整体结果。