6

我有一个“掩码数组”,我想将它添加到另一个数组中——换句话说,我有 3 个数组ABmask。我的问题是存储掩码(作为逻辑数组,作为一个和零的真实数组)的最有效(就执行时间而言)方法是什么?

编辑

这是一个你可以玩的玩具程序(如果你有 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

4 回答 4

5

为什么不使用“哪里”?

where (mask) A = A + B

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

于 2012-05-24T13:06:17.047 回答
3

如果触发器是指浮点运算,那么第一个选项显然更好,因为在这种情况下,每次循环迭代都有 1 个触发器,其中 mask(n) == .true。. 而对于第二个选项,无论掩码(n)的值如何,每次循环迭代都有 2 个触发器。

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

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

where(mask) A = A + B
于 2012-05-24T13:06:44.527 回答
1

没有绝对的方法可以提前确定这一点,因为它完全取决于编译器、平台和问题。仅作为一个例子,通过 Polyhedron 网站上的各种 Fortran 编译器查看基准。

...尽管要注意英特尔的基准测试,因为其中有时会出现“有趣”的事情(例如,几年前,英特尔因故意/秘密地在英特尔 cpu 以外的任何东西上阻碍他们的编译器等而被司法部罚款 1000 万美元) )。

说了这么多,有几点感想:

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

对于您的 Mask 是“密集”与“稀疏”的情况(另见下文),可能存在类似的问题。

2)您的基准参数太小了。基准测试的一个经验法则是,它应该需要至少几到十秒的 exe。使用您的基准测试,执行时间是如此之短,以至于时钟等方面的任何微小差异都会产生较大的相对影响。要清楚地看到这一点,请针对需要几分之一秒、1-2 秒和 7-10 秒的情况运行标准 Linpack 基准测试。通常,1 秒以下的结果对于该示例没有意义。

3) 与 F90/95 结构(ForAll、Where 等)相比,F77 在某些条件下可能(或者至少最近没有测试过)在某些结构上运行得更快。

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

Incidentally, there can be differences in exe even with (apparently) the same compiler. For example, Simply Fortran does quite a good job at producing fast code, it uses GCC/gFortran, compared to say Photran/Eclipse (on Windows), which also uses GCC/gFortran (at least on one of my machines). This could be due to different ways of "interleaving" MingW or CygWin etc, and it could also be due to, for example, Simply Fortran's extensive optimisation for specific chips and instructions sets, etc. Some of those issues don't arise on Linux etc, and so is an example also of "platform" matters.

于 2014-02-11T22:51:16.487 回答
0

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

于 2012-05-30T06:20:49.813 回答