版本C和Fortran90:在MPI_Gather之前填充子阵列的优化

问题描述 投票:1回答:2

我编写了一个小的MPI程序,用2D域上的迭代方案计算:我为每个单元格值(5点模板)应用一个过滤器。

在我的代码中,我根据进程数将全局域拆分为多个子域。我把xcellycell放在每个子域的大小上。

一旦计算达到所需的收敛,我将每个子域的值存储到一维数组中(参见下面的xtemp数组),之后,我使用MPI_Gather将所有这些子域收集到一维最终数组(xfinal数组)中。在下面的assignement(进入循环)中,x0 array代表我想要复制到xtemp 1D array的2D子域。

现在,我想知道我是否应用了良好的编码规则和优化(我的意思是从运行时的角度来获得最佳性能的方法),并在每个版本(语言C和Fortran90)中填充“xtemp”数组。 me是当前MPI过程的等级,xs,xe,ys,ye是每个子域的坐标。我们还必须注意到我们在语言C和Fortran90版本中采用了数组the convention (i,j) = (rows,columns)

我做了:

语言C:

   /* Fill all 1D xtemp array before MPI_Gather all subdomains :
      inner loop on columns index (second index)                                         
      to optimize since C is row major */                                                
   j=1;
   for (i=xs[me];i<=xe[me];i++) {                                                        
      for (k=0;k<ycell;k++)                                                              
         xtemp[(j-1)*ycell+k] = x0[i][ys[me]+k];                                         
      j=j+1;
   }

正如您所看到的,我认为内部索引必须位于列(ys[me]+k)上,因为C语言是行主要的。

Fortran90:

   ! Fill all 1D xtemp array before MPI_Gather all subdomains :
   ! inner loop on rows index (first index)
   ! to optimize since F90 is column major                                               
   i = 1                                                                                 
   do j=ys(me),ye(me)
     xtemp((i-1)*xcell+1:i*xcell) = x0(xs(me):xe(me),j)                                  
     i = i+1
   end do

在这里,我认为内部赋值必须在行(xs(me):xe(me))上,因为F90语言是列专业。

你同意这两个版本的xtemp数组填充,即从优化的角度来看(为了获得两个版本的最小运行时间,F90和C语言)?

更新1:

根据我收到的不同意见,我提供了更多信息:

1.语言C版:

填充For the language C version 1D阵列的xtemp:我使用连续的x0 2D阵列(采用惯例[i,j]=[row,column]):

double **x0;
x0 = malloc(size_total_x*sizeof(*x0));
x0[0] = malloc(size_total_x*size_total_y*sizeof(**x0));
for (i=1;i<size_total_x;i++) {
   x0[i] = x0[0] + i*size_total_y;
}

根据您的指示,以下块有效填写xtemp 1D数组:

j=1;
   for (i=xs[me];i<=xe[me];i++) {                                                        
      for (k=0;k<ycell;k++)                                                              
         xtemp[(j-1)*ycell+k] = x0[i][ys[me]+k];                                         
      j=j+1;
   }

看来你确认了,不是吗?

2. Fortran F90版本:

填充For the F90 version 1D阵列的xtemp:我也使用连续的x0 2D阵列(采用惯例[i,j]=[row,column]):

double precision, allocatable :: x0(:,:)
allocate(x0(0:size_total_x-1,0:size_total_y-1))

关于上面的这个块,它是在Fortran90中分配2D连续数组的正确方法吗?

最后,我没有看到有关使用Fortran90版本填充xtemp 1D阵列的评论,即:

 i = 1                                                                                 
   do j=ys(me),ye(me)
     xtemp((i-1)*xcell+1:i*xcell) = x0(xs(me):xe(me),j)                                  
     i = i+1
   end do

做得更好(从一开始就从正确的优化角度来看):

  j = 1                                                                                 
       do i=xs(me),xe(me)
         xtemp((j-1)*ycell+1:j*ycell) = x0(i,ys(me):ye(me))                                  
         j = j+1
       end do

或不 ???

我意识到,在这两个解决方案之间,输出xtemp将被填充不同(第一个的xcell块和第二个的ycell块)但我不介意它:我只想使用最优化的方式。

谢谢你的帮助

c arrays optimization fortran mpi
2个回答
0
投票

以下是对此问题的各种评论的摘要

Fortran中,或者如果您的数组是C连续数组,另一个选项是使用派生数据类型,因此不需要将数据“打包/解包”到临时缓冲区中。由于数据非常规则(例如2D阵列的子集),因此MPI库应该非常有效地处理这个问题。

Fortran

i = 1                                                                                 
do j=ys(me),ye(me)
  xtemp((i-1)*xcell+1:i*xcell) = x0(xs(me):xe(me),j)                                  
  i = i+1
end do

可能是将x0数组打包到xtemp临时缓冲区的最有效方法。数组赋值语法可以看作是一个隐式循环,它迭代第一个索引(例如Fortran中的列),这是最优的。

另一方面,在C

j=1;
for (i=xs[me];i<=xe[me];i++) {                                                        
  for (k=0;k<ycell;k++)                                                              
    xtemp[(j-1)*ycell+k] = x0[i][ys[me]+k];                                         
  j=j+1;
}

因为(显式)最内层循环迭代在C中最优的第二索引(例如行),所以可能是最优的。请注意,x0数组被声明为锯齿状数组,并且认为是手动构建为连续数组,编译器不知道它。将x0声明为二维阵列(例如double x0[xcell][ycell];)应该会带来更好的表现。

如果您的应用程序是混合MPI + OpenMP,那么在将x0打包/解压缩到xtemp时,可以通过使用OpenMP获得额外的提升。在这种情况下,它可能比使用派生数据类型更快,因为在大多数MPI实现中处理派生数据类型是一个串行操作。


-1
投票

前段时间我写了一些预处理器代码来任意分配基于指针的2D和3D数组。我这样做是为了避免访问成员所需的隐式乘法。请注意,您可以使用此方法以线性方式和维度方式访问数组成员。我希望它有所帮助:

// allocate a 2D array: 'var' is the variable it will be assigned to, 'type' is like float, double, char *, etc
// your var should be declared as type **
// array contents are calloc()ed
// you can linearly address members by &a[0][0];
#define new2DArray(var,type,xDim,yDim) \
do { \
      type *arrayPtr; \
      type **x;      \
      int i; \
      type *p; \
\
      arrayPtr=(type *)calloc(xDim*yDim,sizeof(type)); \
\
      if(arrayPtr==NULL) \
      {  \
         var=NULL; \
         break; \
      }  \
\
      x=(type **)malloc(xDim*sizeof(type **)); \
\
      if(x==NULL)  \
      {  \
        free(arrayPtr);  \
         var=NULL; \
         break;  \
      }  \
\
      for(i=0,p=arrayPtr;i<xDim;i++,p+=yDim) \
         x[i]=p;  \
\
      var=x; \
} while(0)


#define free2DArray(array) do \
{  \
  if(array!=NULL) \
  {  \
    if(array[0]!=NULL) \
      free(array[0]);  \
   free(array);  \
  }  \
} while(0)


#define free3DArray(array) do\
{ \
   if(array!=NULL) \
   {  \
      if(array[0]!=NULL)  \
         free2DArray(array[0]); \
      free(array); \
   } \
} while(0)

// allocate a 3d array: 'var' is the variable it will be assigned to, 'type' is a valid C variable type
// your var should be declared as type ***
// array contents are calloc()ed
// you can linearly address members by &a[0][0][0];
#define new3DArray(var,type,xDim,yDim,zDim) do\
{ \
   type *arrayPtr; \
   type ***xy;  \
   int x,y;  \
   type *p;  \
\
   arrayPtr=(type *)calloc(xDim*yDim*zDim,sizeof(type)); \
   if(arrayPtr==NULL) \
      break; \
   new2DArray(xy,type *,xDim,yDim);  \
   if(xy==NULL)  \
   {  \
      free(arrayPtr);  \
      var=NULL; \
      break; \
   }  \
   p=arrayPtr;  \
   for(x=0;x<xDim;x++) \
      for(y=0;y<yDim;y++,p+=zDim) \
         xy[x][y]=p; \
\
   var=xy; \
} while(0)
© www.soinside.com 2019 - 2024. All rights reserved.