gfortran openmp分段故障发生在基本的do循环上

时间:2012-12-13 23:46:34

标签: parallel-processing fortran gfortran openmp

我有一个将粒子分布到云端网格中的程序。简单地循环遍历粒子总数(Ntot)并填充256 ^ 3网格(即每个粒子分布在8个单元格上)。

% gfortran -fopenmp cic.f90 -o ./cic

编译好。但是当我运行它(./cic)时,我会遇到分段错误。我的循环是一个经典的omp do问题。当我不在openmp中编译它时,该程序有效。

!$omp parallel do
 do i = 1,Ntot
   if (x1(i).gt.0.and.y1(i).gt.0.and.z1(i).gt.0) then
     dense(int(x1(i)),int(y1(i)),int(z1(i))) = dense(int(x1(i)),int(y1(i)),int(z1(i))) &
     + dx1(i) * dy1(i) * dz1(i) * mpart
   end if

   if (x2(i).le.Ng.and.y1(i).gt.0.and.z1(i).gt.0) then
     dense(int(x2(i)),int(y1(i)),int(z1(i))) = dense(int(x2(i)),int(y1(i)),int(z1(i))) &
     + dx2(i) * dy1(i) * dz1(i) * mpart
   end if

   if (x1(i).gt.0.and.y2(i).le.Ng.and.z1(i).gt.0) then
     dense(int(x1(i)),int(y2(i)),int(z1(i))) = dense(int(x1(i)),int(y2(i)),int(z1(i))) &
     + dx1(i) * dy2(i) * dz1(i) * mpart
   end if

   if (x2(i).le.Ng.and.y2(i).le.Ng.and.z1(i).gt.0) then
     dense(int(x2(i)),int(y2(i)),int(z1(i))) = dense(int(x2(i)),int(y2(i)),int(z1(i))) &
     + dx2(i) * dy2(i) * dz1(i) * mpart
   end if

   if (x1(i).gt.0.and.y1(i).gt.0.and.z2(i).le.Ng) then
     dense(int(x1(i)),int(y1(i)),int(z2(i))) = dense(int(x1(i)),int(y1(i)),int(z2(i))) &
     + dx1(i) * dy1(i) * dz2(i) * mpart
   end if

   if (x2(i).le.Ng.and.y1(i).gt.0.and.z2(i).le.Ng) then
     dense(int(x2(i)),int(y1(i)),int(z2(i))) = dense(int(x2(i)),int(y1(i)),int(z2(i))) &
     + dx2(i) * dy1(i) * dz2(i) * mpart
   end if

   if (x1(i).gt.0.and.y2(i).le.Ng.and.z2(i).le.Ng) then
     dense(int(x1(i)),int(y2(i)),int(z2(i))) = dense(int(x1(i)),int(y2(i)),int(z2(i))) &
     + dx1(i) * dy2(i) * dz2(i) * mpart
   end if

   if (x2(i).le.Ng.and.y2(i).le.Ng.and.z2(i).le.Ng) then
     dense(int(x2(i)),int(y2(i)),int(z2(i))) = dense(int(x2(i)),int(y2(i)),int(z2(i))) &
     +  dx2(i) * dy2(i) * dz2(i) * mpart
   end if
  end do
!$omp end parallel do

迭代之间没有依赖关系。想法?

2 个答案:

答案 0 :(得分:2)

此问题以及your other question中的问题来自于启用OpenMP时禁用自动堆阵列的问题。这意味着没有-fopenmp,大数组会自动放入静态存储(称为.bss段),而小数组则在堆栈上分配。当您打开OpenMP支持时,不会使用自动静态分配,并且您的dense数组将在例程的堆栈上分配。 OS X上的默认堆栈限制非常严格,因此会出现分段错误。

这里有几个选项。第一个选项是通过为dense属性赋予SAVE静态分配。另一种选择是通过使它ALLOCATABLE然后使用ALLOCATE语句在堆上显式分配它,例如:

REAL, DIMENSION(:,:,:), ALLOCATABLE :: dense

ALLOCATE(dense(256,256,256))

! Computations, computations, computations

DEALLOCATE(dense)

较新的Fortran版本支持在超出范围时自动释放没有SAVE属性的数组。

请注意,您的OpenMP指令很好,不需要额外的数据共享子句。您不需要在i子句中声明PRIVATE,因为循环计数器具有预定的私有数据共享类。您不需要将其他变量放在SHARED子句中,因为它们是隐式共享的。但是,您在dense上执行的操作应该与ATOMIC UPDATE同步(或者在较早的OpenMP实现上只是ATOMIC),或者您应该使用REDUCTION(+:dense)。原子更新被转换为锁定的添加,并且不应该因为在循环中有条件的巨大减速而导致减速:

INTEGER :: xi, yi, zi

!$OMP PARALLEL DO PRIVATE(xi,yi,zi)
...
if (x1(i).gt.0.and.y1(i).gt.0.and.z1(i).gt.0) then
  xi = int(x1(i))
  yi = int(y1(i))
  zi = int(z1(i))
  !$OMP ATOMIC UPDATE
  dense(xi,yi,zi) = dense(xi,yi,zi) &
                  + dx1(i) * dy1(i) * dz1(i) * mpart
end if
...

使用适当的更改为其他情况复制代码。如果您的编译器抱怨UPDATE构造中的ATOMIC子句,只需删除它。

REDUCTION(+:dense)会在每个线程中创建dense的一个副本,这会消耗大量内存,最终应用的减少会随着dense的大小而变得越来越慢。对于小型数组,它比原子更新效果更好。

答案 1 :(得分:-1)

有关如何使变量共享和私有的说明,请参阅https://computing.llnl.gov/tutorials/openMP/#Clauses

除了必须是私有的循环变量i之外,看起来应该共享所有变量。这将建议使用以下行:

!$omp parallel do default(shared) private(i)

这应该可以解决你的分段错误(假设我得到了所有变量

但是,存在不同线程同时尝试覆盖dense的相同部分的风险,从而导致总计不正确。为了防止出现这种情况,您需要将dense!$omp atomic部分中的每个作业包装到!$omp critical

但是,您可能会发现这些关键部分会导致线程花费大部分时间等待,因此您可能看不到对纯串行代码的任何改进。

原则上,您可以通过使用dense关键字声明reduction来解决此问题,但不幸的是,它不能用于数组。