見出し画像

【C&Fortran演習で学ぶ数値計算(3)】Fortranでの離れたメモリアクセスは実行速度が落ちる

前回の記事の後半でFortranで配列要素へのアクセスの仕方には気を付けましょうというお話を少しだけ触れました。
今回は、実際にどれくらい計算時間に違ってくるのか計測したので記事にしておきます。

まずは前回のおさらい。

C言語とFortranの違い

学生時代にはじめてFortranを触って以来ずっとFortranに慣れてきたので、そういえば配列要素を作成したときにFortranのindexは1から開始するということを忘れていました。
また、C言語とFortranでメモリの並び順も違うんですよね。

C言語の場合、2次元配列array[4][4]を作成するとメモリが連続するのは、array[0][0],array[0][1],array[0][2],array[0][3],array[1][0]・・・・
だから連続するメモリアクセスを行うにあたって、C言語は以下のようにfor文を回せばよいのですが・・・・

    for (i = 0; i < N; i++){
        for ( j = 0; j < N; j++){
            printf("%5.2f",A[i][j]);
        }
    }

Fortranの場合、2次元配列array(4,4)を作成するとメモリが連続するのは、array(1,1),array(2,1),array(3,1),array(4,1),array(1,2)・・・・
だから連続するメモリアクセスを行うにあたって、Fortranは以下のようにdo文を回さなくてはならない・・・・

    do j=1, N
        do i=1,N
            write(*, fmt='(1x, f5.2)', advance='no') A(i,j)
        end do
    end do   

不連続なメモリアクセスは実行速度を落とすのでFortranを使う場合は注意が必要ですね。

こちらの参考書を参考にコードを書いています。

Fortranのプログラム

配列要素へのアクセスは以下のようにしたくなりますよね。

    do i = 1, n
        do j =1, n
            a(i,j) = 10* i + j
            write(*,*)i,j,"",a(i,j)
        end do
    end do

しかし、Fortranの配列の並び順がarray(1,1),array(2,1),array(3,1),array(4,1),array(1,2)・・・・であるため以下のように1つ目の要素から順番にアクセスをしていった方が効率が良いプログラムになります。

    do j = 1, n
        do i =1, n
            a(i,j) = 10* i + j
            write(*,*)i,j,"",a(i,j)
        end do
    end do

では、実際にCPUでの実行速度を計測してみましょう。

program main
    implicit none
    integer, allocatable ::a(:,:)
    integer, parameter::n=2
    integer::i, j
    real(kind=8)::t1, t2

    allocate(a(n,n))

    write(*,*)"========================"
    write(*,*)"離れたメモリでアクセス"
    ! 行列の作成
    call cpu_time(t1)
    do i = 1, n
        do j =1, n
            a(i,j) = 10* i + j
            write(*,*)i,j,"",a(i,j)
        end do
    end do
    call cpu_time(t2)

    write(*,*)"CPU time = ", t2 - t1 

    ! 全要素を出力
    write(*,*)"全要素の出力"
    write(*,*)a

    write(*,*)"========================"
    write(*,*)"メモリの並び順でアクセス"
    ! 行列の作成
    call cpu_time(t1)
    do j = 1, n
        do i =1, n
            a(i,j) = 10* i + j
            write(*,*)i,j,"",a(i,j)
        end do
    end do
    call cpu_time(t2)

    write(*,*)"CPU time = ", t2 - t1 

    ! 全要素を出力
    write(*,*)"全要素の出力"
    write(*,*)a

    deallocate(a) ! メモリの解放
end program main

結果はこちらです。

$ gfortran F_list29.f90
$ ./a.out 
 ========================
 離れたメモリでアクセス
           1           1           11
           1           2           12
           2           1           21
           2           2           22
 CPU time =    1.0399999999999993E-004
 全要素の出力
          11          21          12          22
 ========================
 メモリの並び順でアクセス
           1           1           11
           2           1           21
           1           2           12
           2           2           22
 CPU time =    3.7999999999999839E-005
 全要素の出力
          11          21          12          22

まずwrite(*,*)aで出力した結果が 11 21 12 22となっていることに注意します。
これはarray(1,1),array(2,1),array(1,2),array(2,2)の並び順で配列ができていることを意味しています。

そして結果について、

離れたメモリでアクセスした場合は 、
CPU time = 1.0399999999999993E-004となっています。

一方でメモリの並び順でアクセスした場合は、
CPU time = 3.7999999999999839E-005となり、計算時間は1/3になっています。

今回のような小さな配列では大した差は出てこないですが、3次元配列、4次元配列となるにつれて離れたメモリへのアクセスは実行速度を落とすことになります。

引き続き「C&Fortran演習で学ぶ数値計算」の勉強を進めます。

Twitter➡@t_kun_kamakiri
Instagram➡kamakiri1225
youtube➡https://www.youtube.com/channel/UCbG6_Q9ZRqqVT6YZOpcjDlQ
ブログ➡宇宙に入ったカマキリ(物理ブログ)
ココナラ➡物理の質問サポートサービス
コミュニティ➡製造業ブロガー


この記事が気に入ったらサポートをしてみませんか?