将掩码数组添加到另一个fortran的最有效方法

时间:2012-05-24 12:54:32

标签: performance fortran

我有一个“蒙面数组”,我想添加到另一个数组 - 换句话说,我有3个数组,ABmask。我的问题是什么是最有效的(在执行时间方面)存储掩码的方式(作为逻辑数组,作为1和0的真实数组)?

修改

这是一个你可以玩的玩具程序(如果你有mpif77):

  program main
  implicit None
  include 'mpif.h'
  integer, parameter :: ntry=10000
  integer, parameter :: asize=1000000
  real,dimension(asize) :: A,B,maskr
  logical,dimension(asize) :: mask
  real*8 :: dd,dt,dtave,dtbest
  integer i

  do i=1,asize
     maskr(i)=mod(i,2)
     mask(i)=.False.
     if(mod(i,2).eq.0) mask(i)=.True.
  enddo

  A=1.0; B=1.0
  dtbest=1d33
  dtave=0.0
  do i=1,ntry
     dt=mpi_wtime()
     call add_arrays_logical(asize,A,B,mask)
     dt=mpi_wtime()-dt
     dtbest=min(dt,dtbest)
     dtave=dtave+dt
  enddo
  print*,"==== logical ==="
  print*,"Average",dtave/ntry
  print*,"Best",dtbest

  A=1.0; B=1.0
  dtbest=1d33
  dtave=0.0
  do i=1,ntry
     dt=mpi_wtime()
     call add_arrays_real(asize,A,B,maskr)
     dt=mpi_wtime()-dt
     dtbest=min(dt,dtbest)
     dtave=dtave+dt
  enddo
  print*,"==== Real ==="
  print*,"Average",dtave/ntry
  print*,"Best",dtbest

  A=1.0; B=1.0
  dtbest=1d33
  dtave=0.0
  do i=1,ntry
     dt=mpi_wtime()
     where(mask) A=A+B
     dt=mpi_wtime()-dt
     dtbest=min(dt,dtbest)
     dtave=dtave+dt
  enddo
  print*,"==== Where ===="
  print*,"Average",dtave/ntry
  print*,"Best",dtbest

  end

  subroutine add_arrays_logical(n,A,B,mask)
  integer n
  real A(n),B(n)
  logical mask(n)
  do i=1,n
     if(mask(i))then
        A(i)=A(i)+B(i)
     endif
  enddo
  end

  subroutine add_arrays_real(n,A,B,mask)
  integer n
  real A(n),B(n),mask(n)
  do i=1,n
     A(i)=A(i)+mask(i)*B(i)
  enddo

  end

我的结果:

(gfortran -O2)

==== logical ===
Average  1.52590200901031483E-003
Best  1.48987770080566406E-003
==== Real ===
Average  1.78022863864898680E-003
Best  1.74498558044433594E-003
==== Where ====
Average  1.48216445446014400E-003
Best  1.44505500793457031E-003

(gfortran -O3 -funroll-loops -ffast-math)

==== logical ===
Average  1.47997992038726811E-003
Best  1.44982337951660156E-003
==== Real ===
Average  1.40655457973480223E-003
Best  1.37186050415039063E-003
==== Where ====
Average  1.48403010368347165E-003
Best  1.45006179809570313E-003

(pfg90 -fast) - 在很旧的机器上

==== logical ===
Average   5.4871437072753909E-003
Best   5.4519176483154297E-003
==== Real ===
Average   4.6096980571746831E-003
Best   4.5847892761230469E-003
==== Where ====
Average   5.3572671413421634E-003
Best   5.3288936614990234E-003

(pfg90 -O2) - 在很旧的机器上

 ==== logical ===
 Average   5.4929971456527714E-003
 Best   5.4569244384765625E-003
 ==== Real ===
 Average   5.5974062204360965E-003
 Best   5.5701732635498047E-003
 ==== Where ====
 Average   5.3811835527420044E-003
 Best   5.3341388702392578E-003

当然,有一些事情可以影响这一点 - 例如编译器对循环进行矢量化的能力 - 那么是否有关于如何实现这样的事情的经验法则呢?

4 个答案:

答案 0 :(得分:5)

为什么不使用“where”?

where (mask) A = A + B

可能使用面具是最快的,但唯一可以确定的方法是测量。

答案 1 :(得分:3)

如果用flops表示浮点运算,则第一个选项明显更好,因为在这种情况下,每次循环迭代有1次翻转,其中mask(n)== .true。 。而对于第二个选项,无论mask(n)的值如何,每个循环迭代都有2个触发器。

OTOH,如果您对最大限度地减少执行此功能所花费的时间感兴趣,为什么不在数据上尝试两个版本并测试哪个版本更快?

您可能还想测试使用Fortran 90+ WHERE构造的版本

where(mask) A = A + B

答案 2 :(得分:1)

没有绝对的方法可以提前确定,因为它依赖于编译器,平台和问题。仅作为一个例子,请参阅Polyhedron网站上各种Fortran编译器的基准测试。

虽然在英特尔的基准测试中采取了一些谨慎态度,但有时候会有“有趣”的事情(例如,几年前英特尔被司法部罚款1000万美元,故意/隐蔽地阻止他们的编译器。英特尔CPU等。)

说了这么多,想了几句:

1)有些策略的“设置成本”较高,而其他策略的“执行成本”较高。也就是说,编译器/代码需要一些时间将正确的位发送到cpu / micro指令,这需要不同的策略时间。因此,在添加短数组时,一种策略可能会做得更好,而对于长数组,另一种策略可能会做得更好。

对于面具“密集”而非“稀疏”的情况,可能存在类似问题(另见下文)。

2)您的基准参数太小了。基准测试的一个经验法则是它应该至少需要几十秒的exe。使用您的基准测试,执行时间非常短,以至于时钟等的任何微小差异都会产生很大的相对影响。为了清楚地看到这一点,请运行标准的Linpack基准测试,以满足需要一秒,1-2秒和7-10秒的情况。通常,1秒内的结果对于该示例而言毫无意义。

3)与F90 / 95构建体(ForAll,Where等)相比,F77在某些条件下在某些构建体上运行速度更快(或者至少没有进行过测试)。

如果您的编译器允许使用数组部分作为索引,那么有时(例如当Mask是“稀疏”时)最好先创建一个保存数组索引为True的整数数组,然后只添加True数组部分。例如,如果Integer iIndex(:)包含.True。 Mask的索引值,那么像A(iIndex(“bounds”))+ B(iIndex(“bounds”))这样的东西可以非常有效,因为与一般算术运算相比,用逻辑创建索引的成本较小。因此,尽管额外的“设置”成本,如果它是一个“稀疏”问题,那么可能只需要少量添加等。乘法和更高的算术甚至更“昂贵”,因此在稀疏变化时更加明显。这是一种“自制”Where / ForAll构造。

顺便说一句,即使(显然)相同的编译器,exe也可能存在差异。例如,Simply Fortran在生成快速代码方面表现相当不错,它使用GCC / gFortran,而Photran / Eclipse(在Windows上)也是如此,它也使用GCC / gFortran(至少在我的一台机器上)。这可能是由于“交错”MingW或CygWin等的不同方式,也可能是由于,例如,Simply Fortran对特定芯片和指令集的广泛优化等等。其中一些问题不会出现在Linux上等等,也是“平台”问题的一个例子。

答案 3 :(得分:0)

A = A + maskr*B怎么样?这完全消除了任何条件行为,代价是扫描所有三个数组而不管掩码的稀疏性。