6

別の配列に追加したい「マスクされた配列」があります。つまり、3 つの配列、ABおよびがありmaskます。私の質問は、マスクを (論理配列として、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

4 に答える 4

5

「どこ」を使ってみませんか?

where (mask) A = A + B

おそらくマスクを使用するのが最も速いですが、確実に知る唯一の方法は測定することです。

于 2012-05-24T13:06:17.047 に答える
3

フロップで浮動小数点演算を意味する場合は、最初のオプションの方が明らかに優れています。その場合、ループの反復ごとに1フロップがあり、mask(n)==.trueです。。一方、2番目のオプションでは、mask(n)の値に関係なく、ループの反復ごとに2フロップがあります。

OTOH、この関数の実行にかかる時間を最小限に抑えることに関心がある場合は、データで両方のバージョンを試して、どちらか速い方をテストしてみませんか?

また、Fortran90+WHERE構文を使用するバージョンをテストすることもできます。

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

コンパイラ、プラットフォーム、および問題に依存するため、これを事前に決定する絶対的な方法はありません。1 つの例として、Polyhedron の Web サイトで、さまざまな Fortran コンパイラによるベンチマークを参照してください。

... ただし、Intel のベンチマークには「おかしな」ことが時々あるため、注意が必要です (たとえば、数年前、Intel は、Intel CPU 以外のものでコンパイラを故意に/密かに妨害したとして、司法省から 1,000 万米ドルの罰金を科されました)。 )。

そうは言っても、いくつかの考え:

1) 一部の戦略は「セットアップ コスト」が高く、他の戦略は「実行コスト」が高くなります。つまり、コンパイラ/コードは、適切なビットを cpu/マイクロ命令に送信するのに時間がかかります。これには、さまざまな戦略でさまざまな時間がかかります。そのため、短い配列を追加する場合は 1 つの戦略がうまくいくかもしれませんが、長い配列を追加する場合は別の戦略がうまくいくかもしれません。

マスクが「密度が高い」場合と「密度が低い」場合には、同様の問題が発生する可能性があります (以下も参照)。

2) ベンチマーク パラメータが小さすぎます。ベンチマークの経験則の 1 つは、少なくとも数秒から 10 秒の exe が必要であるということです。ベンチマークでは、実行時間が非常に短いため、クロックなどのわずかな違いが相対的に大きな影響を与える可能性があります。これを明確に確認するには、数分の 1 秒、1 ~ 2 秒、および 7 ~ 10 秒を必要とするケースに対して、標準の Linpack ベンチマークを実行します。一般に、1 秒未満の結果は、その例では意味がありません。

3) F90/95 コンストラクト (ForAll、Where など) と比較して、いくつかのコンストラクトでは F77 が一部の条件下でより高速に実行される可能性があります (または、少なくとも最近はテストされていません)。

コンパイラが配列セクションをインデックスとして使用することを許可している場合、True である配列インデックスを保持する整数配列を最初に作成し、次に True 配列セクションのみを追加する方が良い場合があります (たとえば、Mask が「スパース」の場合)。たとえば、Integer iIndex(:) が .True. Mask のインデックス値の場合、 A(iIndex("bounds")) + B(iIndex("bounds")) のようなものは非常に効率的です。これは、論理値を使用してインデックスを作成するコストが一般的な算術演算に比べて小さいためです。したがって、追加の「セットアップ」コストにもかかわらず、それが「まばらな」問題である場合、必要な追加などの数は少ないかもしれません。乗算とより高度な算術演算はさらに「費用がかかる」ため、バリエーションが少ない場合はさらに顕著になります。これは一種の「自家製」です。

ちなみに、(どうやら) 同じコンパイラでも exe に違いがある場合があります。たとえば、Simply Fortran は高速なコードを生成するのに非常に優れています。GCC/gFortran を使用しますが、GCC/gFortran を使用する Photran/Eclipse (Windows の場合) と比較して、GCC/gFortran も使用します (少なくとも私のマシンの 1 台では)。これは、MingW や CygWin などを「インターリーブ」するさまざまな方法が原因である可能性があります。また、たとえば、Simply Fortran が特定のチップや命令セットなどを広範囲に最適化したことが原因である可能性もあります。これらの問題のいくつかは、Linux では発生しません。など、「プラットフォーム」の問題の例でもあります。

于 2014-02-11T22:51:16.487 に答える
0

どうA = A + maskr*Bですか?これにより、条件付きの動作が完全に削除されますが、マスクのスパース性に関係なく、3 つの配列すべてをスキャンする必要があります。

于 2012-05-30T06:20:49.813 に答える