3 計算時間(パフォーマンス)について
数値計算において正しい結果が得られる事は最も大切ですが、 同程度の誤差の範囲内でより短時間で結果が得られるのであればそれに越したことはありません。
パフォーマンスを追求する事は一般的に多大な労力が必要となります。 キャッシュの有効活用や処理の並列化など高度な技術やノウハウも必要となります。
そのためこのセクションでは比較的簡単に取り入れる事ができ、且つ有効な2つの方法のみを示します。
3.1 既に存在する解法を活用する
計算精度についてのセクションでも触れましたが、より短時間で計算結果を得るためにも「先人の知恵」が役立ちます。 自分の行いたい計算にパフォーマンスを考慮したより良い解法(アルゴリズム)が存在するかどうかを調べてみる価値は十分あります。一例として行列積を求める場合を考えます。
行列積は以下の計算を行うものです。
普通に思いつく方法で上記計算を行おうとすると例えば以下の様なコードが考えられます。
function matrix_mult(a,b) result(c)
real,intent(in) :: a(:,:), b(:,:)
real c(size(a,dim=1),size(b,dim=2))
integer i, j, k
do j = 1, size(b,dim=2)
do i = 1, size(a,dim=1)
c(i,j) = 0
do k = 1, size(a,dim=2)
c(i,j) = c(i,j) + a(i,k) * b(k,j)
enddo
enddo
enddo
end function matrix_mult
この方法でも全く問題なく結果を得ることができます。
しかし行列積の計算にもやはり既に知られた解法でより良いパフォーマンスのものが存在します。
ここでは詳しく説明をしませんが、 行列を複数のブロックに分けて考えてブロック毎に計算を行いキャッシュを有効活用するような解法(ブロック化を行う解法)が存在します。
以下にブロック化を行う解法と、 上記に示した最も一般的に思いつくと思われる解法との比較を示します。
行列サイズ3000においては10倍以上のパフォーマンスの違いが見られます。 このように優れた解法(アルゴリズム)が存在する場合、それを活用する事はとても有効な方法です。
3.2 配列のアクセス順序が連続的になるようにする
パフォーマンスに関連して、もう一つ知っておくと役立つものに「多次元配列のアクセス順序」があります。Fortranで多次元配列の各成分をアクセスする場合に、 左側の添字から順番に変更されるように(内側のループインデックスを左側にするように)すると一般的にパフォーマンスが良くなります。
例えば以下のコード例で
a(i1, i2, i3)
とするよりも
a(i3, i2, i1)
とする方が一般的にパフォーマンスが良くなります。
real a(100,100,100)
do i1=1,100
do i2=1,100
do i3=1,100
! a(i1,i2,i3) = a(i1,i2,i3)+1.0 ! 遅い!
a(i3,i2,i1) = a(i3,i2,i1)+1.0 ! こちらがベター
end do
end do
end do
これはメモリ領域を(飛び飛びにではなく)なるべく連続的にアクセスし、 それによってキャッシュがより有効に活用されることに関係しています。 Fortranの多次元配列のメモリ内の順番は列優先(Column Major) です。
例えば3行4列の2次元整数配列は integer a(3,4) のように宣言され、メモリ上には以下の順番で数値が格納されます。
a(1,1) a(2,1) a(3,1) a(1,2) a(2,2) a(3,2) a(1,3) a(2,3) a(3,3) a(1,4) a(2,4) a(3,4)
参考図
ご参考までに以下のサンプルをある環境で実行した場合の実行時間を示します。
| 13行目の記述 | 実行時間 | |
| a(k, j, i) | 15.92秒 | |
| a(i, j, k) | 1.11秒 |
このように配列のアクセス順序を連続的になるように注意する事でより良いパフォーマンスが得られます。
[
array-access.f90
] - 配列成分アクセス順序に関するサンプル
program array_access
implicit none
integer,parameter :: n = 512
integer a(n,n,n)
integer val, i, j, k
real t1, t2
print *, "Enter a value:"
read *, val
call cpu_time(t1)
do k=1, n
do j=1, n
do i=1, n
a(i,j,k) = val
end do
end do
end do
call cpu_time(t2)
print *, "Time was:", t2-t1
end program array_access
出力例:
Enter a value:
55
Time was: 1.1388073
前へ 上へ 次へ
