29
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FortranAdvent Calendar 2018

Day 24

Fortranにおける実行時間の測定

Last updated at Posted at 2018-12-23

概要

Fortranでプログラムの実行時間を測定する方法とそれに用いるサブルーチン/関数についてまとめました.

使用環境

コンパイラ バージョン
intel Parallel Studio XE Composer Edition for Fortran 17.0.4.210
PGI Visual Fortran for Windows 18.7
gfortran 7.3.0 (for Ubuntu on WSL)

更新履歴

  • 2018年12月24日 補足を追記
  • 2018年12月25日 誤字修正

時間測定を行うサブルーチン

Fortranには,実行時間の測定に利用できるサブルーチンが三つ用意されています.

サブルーチン 機能
system_clock([count=現在のカウント, count_rate=1秒あたりのカウント, count_max=カウントの最大値) ある基準時点からの経過時間をカウントという単位(整数)で取得する
count_rate引数に整数型変数を渡すと1秒あたりのカウント数が得られる
count_maxはカウントの最大値であり,これを越えるとカウントが0に戻る
cpu_time(time=経過秒数) ある基準時点からのCPU時間を取得する
date_and_time([date=年月日, time=時分秒ミリ秒, zone=時差, values=年月日時差時分秒ミリ秒]) サブルーチン呼出し時点の年月日と時刻(時,分,秒,ミリ秒)取得する
dateはyyyymmdd形式の文字列
timeはhhmmss.fff形式の文字列
zoneはshhmm形式の標準時からの時差の文字列
valueは要素数8の整数型配列で,それぞれ年, 日, 時, 差時(分), 分, 秒, ミリ秒を持つ

もう一つ,Fortranコンパイラであれば大抵は使える関数があるので,その関数を使った測定方法を後ほど紹介します.

sytem_clock

サブルーチンsystem_clock()を用いると,ある基準時点からの経過時間をカウントという単位(整数)が得られます.1秒間に何回カウントされるかも取得できます.system_clock()を用いてプログラムの実行時間を測定するには,以下の手順と実装が考えられます.

  1. 測定したい処理を実行する直前でsystem_clock()を呼ぶ.このとき,1秒あたりのカウント数も取得しておく.
  2. 測定したい処理が終了したらsystem_clock()を呼ぶ.
  3. 終了後のカウントと実行直前のカウントの差を計算し,1秒あたりのカウント数で割って秒に変換する.
program main
    use,intrinsic :: iso_fortran_env
    implicit none

    integer(int32) :: time_begin_c,time_end_c, CountPerSec, CountMax
    
    call system_clock(time_begin_c, CountPerSec, CountMax)
    call sleep(1)
    call system_clock(time_end_c)

    print *,time_begin_c,time_end_c, CountPerSec,CountMax
    print *,real(time_end_c - time_begin_c)/CountPerSec,"sec"
end program main

時間の測定に用いる変数は,末尾に_cとして単位を付けています.処理の開始/終了をstart/stopと書くべきか,begin/endと書くべきかの議論は本書の対象ではありません.
sleep(秒数)は,引数で与えた秒数だけプログラムの実行を停止します.実数を与えてしまうと,整数に変換されるわけでもなく異様に長い時間停止するので注意が必要です.

各コンパイラでの実行結果は以下のようになりました.

Intel
   831208950   831219030       10000  2147483647
   1.008000     sec

PGI
            0      1010999      1000000   2147483647
    1.010999     sec

gfortran
    12245274    12246275        1000  2147483647
   1.00100005     sec

この測定方法はいくつか注意点があります.

  • 上の実行例からもわかりますが,調査した範囲では1秒あたりのカウント数は全てのコンパイラで異なるので,必ずsystem_clock()で取得する必要があります.
  • 引数に与える整数型変数の種別によって得られる値が変化します.
  • カウントは上限値count_maxを超えると0に戻るので,処理の実行中に上限値を越えてしまうと経過時間が負になるなど,正しい値が得られません.この対策は,integer(int64)を用いることです.
  • 上に関連して,基準時点は全てのコンパイラで異なります.

つまり,system_clock()で得られる数値(カウント,1秒あたりのカウント)の可搬性は皆無であることを把握しておきましょう.

1秒あたりのカウント数

system_clock()で利用可能な整数型の種別と,そのときに得られる各種パラメータの値を表にまとめました.

整数型の種別 Count Rate [clock/s] Count Max [count] 測定上限 備考
int16 1000 32767 約33秒 Intelコンパイラのみ利用可能12
int32 100003
10000004
10005
2147483647 約2.5日3
約36分4
約25日5
int64 10000003
100000004
10000000005
9223372036854775807 約290000年3
約29000年4
約290年5

Intelコンパイラでinteger(int16)を用いてsleep(30)の実行時間を測定したところ,途中でカウントが上限値32767を越えて0に戻り,実行時間が負になりました.

  4961   2977   1000  32767
  -1.984000     sec

この問題を回避するために,system_clock()で実行時間を測定するときはinteger(int64)を使うようにします.さすがに290年連続で動かすことはないでしょう.

1カウントが最小の時間測定の単位と考えられますが,Windowsプラットフォームではマイクロ秒単位での測定はできていないようです.

system_clockの基準時点

system_clock()はある基準時点からの経過時間をカウントという単位で返します.その基準時点もコンパイラによって異なります.

コンパイラ 基準時点
Intel 協定世界時(UTC)の1970年1月1日の00:00
PGI 1回目のsystem_clockが実行された時点
1回目のsystem_clock()のカウントは0
gfortran OS(Ubuntu on WSL)の起動時

上で示した実行結果を再掲しますが,PGIコンパイラは1回目のカウントが0になっていることがわかります.

PGI
            0      1010999      1000000   2147483647
    1.010999     sec

Intelコンパイラでコンパイルしたプログラムを2018年12月22日午前1時30分に実行すると,

      1545410046972000      1545410047974000               1000000   9223372036854775807
   1.002000     sec

という結果が得られました.1545410046972000カウントを1000000 count/sで除して単位を換算すると,約49年という値が得られました.

cpu_time

サブルーチンcpu_time()を用いると,ある基準時点からのCPU時間が秒の単位で得られます.秒とはいっても,引数として渡すのは実数なので,1秒未満の時間は小数点以下の値として得られます.system_time()と同様に,cpu_time()を用いて以下の手順で実行時間を測定します.

  1. 測定したい処理を実行する直前でcpu_time()を呼ぶ.
  2. 測定したい処理が終了したらcpu_time()を呼ぶ.
  3. 終了後の時間と実行直前の時間の差を計算する.

秒単位で取得できるので,とても楽です.テストプログラムを下のように実装しました.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real32)   :: time_begin_s,time_end_s

    call cpu_time(time_begin_s)
    call sleep(1)
    call cpu_time(time_end_s)

    print *,time_begin_s, time_end_s
    print *,time_end_s - time_begin_s,"sec"
end program main

時間の測定に用いる変数は,末尾に_sとして単位を付けています.

得られた数値の換算をする必要はありませんが,実行結果を見ると,これも厄介なサブルーチンであることがわかります.

Intel
  0.0000000E+00  0.0000000E+00
  0.0000000E+00 sec

PGI
    0.000000        1.005000
    1.005000     sec

gfortran
   1.56250000E-02   1.56250000E-02
   0.00000000     sec

Intelコンパイラとgfortranではsleep()前後の時間が等しく,その結果実行時間が0になっています.cpu_time()はその名前の通り,CPU時間を取得します.sleep()はCPUを(正確にはプロセス)を停止するので,CPU時間が経過しないことが原因だと考えられます.つまり,cpu_time()で実行時間を測定すると,実時間(時計の経過時間)ではなく,実時間からCPUが働いていない時間(sleepread*による標準入力待ちなど)を除いた時間が得られます.

これがよい挙動かどうかは測定したい処理によって異なるでしょう.全てのコンパイラで挙動が一致していれば問題ありませんが,調査した範囲では,Intelコンパイラとgfortranは上述の挙動ですが,PGIコンパイラはread*の入力待ち時間も測定しており,どうも実時間が得られるようです.

cpu_timeの基準時点

system_clock()の基準時点と同様に,cpu_time()の基準時点はコンパイラで異なります.数値から推察すると,Intelコンパイラおよびgfortranはプログラム実行時,PGIコンパイラは1度目のcpu_time()を呼び出した時点と思われます6

それを確認するために,cpu_time()を呼び出す前に時間のかかる処理を置いてみます.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    real(real32)   :: time_begin_s,time_end_s

    integer,parameter :: N = 2**8
    real(real32),allocatable :: a(:,:), b(:,:), c(:,:)
        
    allocate(a(N,N))
    allocate(b(N,N))
    allocate(c(N,N))

    call random_number(a)
    call random_number(b)
    c = matmul(a,b)
    print *,c

    call cpu_time(time_begin_s)
    call random_number(a)
    call random_number(b)
    c = matmul(a,b)
    print *,c
    call cpu_time(time_end_s)

    print *,time_begin_s, time_end_s
    print *,time_end_s - time_begin_s,"sec"

    deallocate(a)
    deallocate(b)
    deallocate(c)
end program main

行列の要素をすべて乱数で決定し,それらの行列の行列-行列積を計算し,全要素を画面に出力しました.time_begin_s, time_end_sの値が変化し,経過時間が測定できました.

Intel
  7.8125000E-02  0.2031250
  0.1250000     sec
PGI
    0.000000        3.374000
    3.374000     sec

gfortran
   6.25000000E-02   9.37500000E-02
   3.12500000E-02 sec

date_and_time

サブルーチンdate_and_time()を用いると,サブルーチン呼出し時点の年月日と時刻(時,分,秒,ミリ秒)が得られます.引数を選ぶことで,欲しい情報を選んで取得できます.

とりあえず,どのような結果が得られるのかを確認します.

program main
    use,intrinsic :: iso_fortran_env
    implicit none

    character(8)  :: date ! yyyymmdd
    character(10) :: time ! hhmmss.fff
    character(5)  :: zone ! shhmm
    integer :: value(8)   ! yyyy mm dd diff hh mm ss fff

    call date_and_time(date, time, zone, value)
    print *,date
    print *,time
    print *,zone
    print *,value
end program main
Intel
 20181222
 221228.368
 +0900
        2018          12          22         540          22          12          28         368

PGI
 20181222
 221234.274
 +0000
         2018           12           22            0           22           12           34          274

gfortran
 20181222
 221237.809
 +0900
        2018          12          22         540          22          12          37         809

date, timeは数値のように見えますが,文字列です.zoneは標準時からの時差で,Intelコンパイラおよびgfortranは協定世界時,PGIコンパイラはおそらくOSのタイムゾーンを標準時(日本の場合はJST)としています.

個別の値を数値として得るには,values引数に整数型配列を渡します.上の実行結果では,配列value(8)の各要素に整数値が得られています.時差はzoneとは異なり,単位は分です.上の実行結果では,+9時間が540になっていることが確認されます.

values引数を用いる場合には,enumで添字に有意な名前を付けておくと利便性が向上します.

program main
    use,intrinsic :: iso_fortran_env
    implicit none
   
    enum, bind(c)
        enumerator :: Year = 1
        enumerator :: Month
        enumerator :: Day
        enumerator :: TimeDifference_min
        enumerator :: Hour
        enumerator :: Minute
        enumerator :: Second
        enumerator :: Millisecond
    end enum

    integer :: value(8)

    call date_and_time(values = value)
    print '(I2,A,I2)',value(Hour),":",value(Minute) ! 22:15
end program main

system_clock()およびcpu_time()と同様に,date_and_time()を用いて実行前後の年月日時分秒ミリ秒を得ると,経過時間を計算できます.

  1. 測定したい処理を実行する直前でdate_and_time(values=...)を呼ぶ.
  2. 測定したい処理が終了したらdate_and_time(values=...)を呼ぶ.
  3. 終了後の年月日時分秒ミリ秒と実行直前の年月日時分秒ミリ秒から経過時間を計算する.

date_and_time()はシステム時間を取得できるので,人間が体感する形でプログラムの実行時間を取得するのに都合がよいのですが,処理の実行前後の年月日時分秒ミリ秒から経過時間を計算する処理が必要になります.system_clock()cpu_time()のように単純ではありません.日を跨ぐ程度なら大したことはありませんが,月を跨ぐ場合には,各月の日数が必要になります.閏年の時に2月を跨ぐ場合はどうするかなどを考え始めると,ちょっと面倒くさそうです.values引数で取得した配列を引き算したら,スパッと経過時間を出してくれると楽だったのですが7

第4の方法(OpenMPを利用する方法)

さて,Fortran標準で実行時間を測定する方法を3通り見てきました.単純に実行時間を測定したいだけなのに,どれも決め手に欠けます.system_clock()cpu_time()はコンパイラ間の可搬性がありません.詳しくは後述しますが,プログラムを並列化して実行すると,正しく測定できません.cpu_time()は実時間が取得できません.date_and_time()は実行時間の計算が面倒です.

OpenMPのomp_get_wtime()関数を利用すると,実行時間を実時間で簡単に取得できます.OpenMPはFortranの標準機能ではありませんが,大抵のFortranコンパイラで利用できるはずです.

OpenMPは共有メモリ計算機においてプログラムのマルチスレッド並列化に用いるインタフェースです.並列化したい箇所に指示句(ディレクティブ)を挿入することで,コンパイラに並列化を指示します8.OpenMPに対応していないコンパイラあるいはOpenMPのディレクティブを処理しないようにすると,ディレクティブはコメント扱いになって無視されます.そのため,逐次プログラムと並列プログラムを共通のソースコードで管理できるという利点があります.

単純なdoループを並列化するには次のようにディレクティブを挿入します.

!$omp parallel do
do i = 1, N
    c(i) = a(i) + b(i)
end do
!$omp end parallel do

もしくは

!$omp parallel workshare
c = a + b
!$omp end parallel workshare

とも書けます.

コンパイラにOpenMPのディレクティブを処理させるには,オプションを付与します.

コンパイラ オプション
Intel /Qopenmp
PGI -mp
gfortran -fopenmp

omp_get_wtime

紹介してきた方法は全てサブルーチンでしたが,omp_get_wtime()は関数です.利用するにはomp_libuseする必要があります.

サブルーチン 機能
omp_get_wtime() 過去のある時点9からの経過秒数を倍精度で返す

下のように実装し,コンパイルオプションを付けてコンパイルすると,実行時間が得られます.

program main
    use,intrinsic :: iso_fortran_env
    use omp_lib
    implicit none

    real(real64)   :: time_begin_s,time_end_s

    time_begin_s = omp_get_wtime()
    call sleep(1)
    time_end_s = omp_get_wtime()

    print *,time_begin_s,time_end_s
    print *,time_end_s - time_begin_s,"sec"
end program main
Intel
   522309.037275617        522310.055093688
   1.01781807123916      sec

PGI
    0.000000000000000         1.011729508463759
    1.011729508463759      sec

gfortran
   4556.2882710000003        4557.2884759999997
   1.0002049999993687      sec

ところが,コンパイルオプションを付けないと,omp_get_wtime()の参照エラーが生じます.OpenMPの利点は,コンパイルオプションの有無で逐次実行と並列実行を切り替えられることです.コンパイルオプションの有無でエラーが出るのは好ましくありません.

そこで,OpenMPのディレクティブを処理するオプションが付与された時にだけコンパイルされるよう,ディレクティブセンチネル10!$を導入します.

program main
    use,intrinsic :: iso_fortran_env
    !$ use omp_lib
    implicit none

    !$ real(real64)   :: time_begin_s,time_end_s

    !$ time_begin_s = omp_get_wtime()
    call sleep(1)
    !$ time_end_s = omp_get_wtime()

    !$ print *,time_begin_s,time_end_s
    !$ print *,time_end_s - time_begin_s,"sec"
end program main

今度はオプションがない場合には何も表示されなくなり,オプションを付けたときのみ実行時間が表示されるようになるはずです.

並列プログラムの実行時間の測定

Fortranが高度なプログラミング言語かどうかは,何ができたら高度かを定義できないと議論できませんが,Fortranの利用が想定されているのは,数値計算分野で現れるでかい配列に対する並列計算です.せっかくなので,OpenMPを用いて並列計算を行うプログラムの処理時間を,各手続を用いて測定してみます.

下のプログラムでは,要素数$2^{13}\times 2^{13}$の配列a,b,cの足し算を並列に実行します.スレッド数はomp_set_num_threads(スレッド数)を利用して,1,2,4と変化させます.
実行時間の測定は,ここまでと同じように足し算を実行するループの前後で各手続を呼出し,その差を取ることで得ます.時間測定のための手続を呼び出す際,omp masterディレクティブを利用して,マスタースレッドのみが呼出しを行うようにしています.

program main
    use,intrinsic :: iso_fortran_env
    !$ use omp_lib
    implicit none

     enum, bind(c)
        enumerator :: Year = 1
        enumerator :: Month
        enumerator :: Day
        enumerator :: TimeDifference_min
        enumerator :: Hour
        enumerator :: Minute
        enumerator :: Second
        enumerator :: Millisecond
    end enum

    integer(int32),parameter :: N = 2**13
    real(real64),allocatable :: a(:,:),b(:,:),c(:,:)
    integer(int32) :: i,j

    integer(int64) :: time_begin_c,time_end_c,CountPerSec
    real(real32)   :: time_begin_s,time_end_s
    !$ real(real64)   :: time_begin_ws,time_end_ws     ! 変数名の重複を避けるため,wall-clock timeのwを追加
    integer :: time_begin_values(8),time_end_values(8) !標準種別

    allocate(a(N,N))
    allocate(b(N,N))
    allocate(c(N,N))

    ! 起動するスレッド数
    !$ call omp_set_num_threads(4)

    !$omp parallel
    ! 配列の初期化
    !$omp do
    do i = 1,N
    do j = 1,N
        a(i,j) = 1._real64
        b(i,j) = 2._real64
        c(i,j) = 0._real64
    end do
    end do
    !$omp end do

    ! system_clock
    !$omp master
    call system_clock(time_begin_c)
    !$omp end master
    !$omp do
    do i = 1,N
    do j = 1,N
        c(i,j) = a(i,j) + b(i,j)
    end do
    end do
    !$omp end do
    !$omp master
    call system_clock(time_end_c,CountPerSec)
    print *,real(time_end_c - time_begin_c)/CountPerSec,"sec",sum(c)/N/N
    !$omp end master

    ! cpu_time
    !$omp master
    call cpu_time(time_begin_s)
    !$omp end master
    !$omp do
    do i = 1,N
    do j = 1,N
        c(i,j) = a(i,j) + b(i,j)
    end do
    end do
    !$omp end do
    !$omp master
    call cpu_time(time_end_s)
    print *,time_end_s - time_begin_s,"sec",sum(c)/N/N
    !$omp end master
    
    ! date_and_time
    !$omp master
    call date_and_time(values = time_begin_values)
    !$omp end master
    !$omp do
    do i = 1,N
    do j = 1,N
        c(i,j) = a(i,j) + b(i,j)
    end do
    end do
    !$omp end do
    !$omp master
    call date_and_time(values = time_end_values)
    print *,time_end_values(     Second)-time_begin_values(     Second) &
          +(time_end_values(Millisecond)-time_begin_values(Millisecond))/1000.0,"sec",sum(c)/N/N
    !$omp end master

    ! omp_get_wtime
    !$omp master
    !$ time_begin_ws = omp_get_wtime()
    !$omp end master
    !$omp do
    do i = 1,N
    do j = 1,N
        c(i,j) = a(i,j) + b(i,j)
    end do
    end do
    !$omp end do
    !$omp master
    !$ time_end_ws = omp_get_wtime()
    !$ print *,real(time_end_ws - time_begin_ws,real32),"sec",sum(c)/N/N
    !$omp end master
    !$omp end parallel

    deallocate(a)
    deallocate(b)
    deallocate(c)
end program main

配列cの平均を画面出力しているのは,最適化によって足し算のループ自体が無視されることを避けるためです.
それぞれのコンパイラで以下のようなオプションをつけてコンパイルし,作成された実行ファイルを実行しました.

コンパイラ オプション
Intel /O2 /Qopenmp
PGI -mp -fast -tp=px
gfortran -fopenmp -Ofast

Intelコンパイラの最適化レベルは,他のコンパイラより1段階低くなっています.PGIコンパイラの-tp=pxはIntel x86アーキテクチャを対象にしたコードを生成するオプションです.

ループ順序の違いの影響は,補足で言及しています

測定結果を表に示します.各コンパイラでコンパイルした後,date_and_timeの測定時間が負でない結果が5個得られるまで実行して得た結果から最大値と最小値をのぞき,残った3個を平均した値です.gfortranだけ著しく遅いのですが,これは間違いではありません11

コンパイラ 時間測定の手続 実行時間 [s]
1スレッド
実行時間 [s]
2スレッド
実行時間 [s]
4スレッド
Intel system_clock 0.133 0.073 0.063
cpu_time 0.125 0.125 0.125
date_and_time 0.130 0.067 0.033
omp_get_wtime 0.130 0.066 0.033
PGI system_clock 0.110 0.064 0.051
cpu_time 0.108 0.057 0.029
date_and_time 0.109 0.058 0.029
omp_get_wtime 0.109 0.058 0.029
gfortran system_clock 3.145 1.646 1.045
cpu_time 3.130 3.260 3.859
date_and_time 3.139 1.692 0.959
omp_get_wtime 3.142 1.641 0.937
  • cpu_timeを用いると,Intelコンパイラとgfortranはスレッド数を増加させても実行時間が減少しません.cpu_timeはCPU時間を取得するため,CPUの複数コアが動作した場合に各コアの時間の合計が得られているのではないかと考えられます.一方で,PGIコンパイラではdate_and_timeomp_get_wtimeと同様の結果が得られています.PGIコンパイラの実装がアレなだけでは・・・
  • system_clockによる測定結果はスレッド数の増加とともに減少しますが,2スレッド→4スレッドでの減少は他の手続による結果と比較して少なく,実時間よりも大きな値が得られます.こうなる理由はわかりません.
  • date_and_timeは概ねスレッド数に反比例して実行時間が減少します.
  • omp_get_wtimedate_and_timeとほぼ同じ結果が得られています.

並列実行時に実時間で実行時間を取得できるのはdate_and_time()omp_get_wtime()です.両者のうち,omp_get_wtime()の方が,二つの時点での測定結果から実行時間を簡単に計算できます.ただし,omp_get_wtime()はモジュールの利用とコンパイルオプションの付与という一手間が必要です.

#まとめ
簡便でかつ並列実行時に正しく時間が測定できるサブルーチン/関数はありません.いずれのサブルーチン/関数にも,正しく計測できない・手間がかかるなどの問題はありますが,本記事の結果より,逐次実行時の実行時間の測定には(入力待ちなどがないのであれば)cpu_time(),並列実行時にはomp_get_wtime()を用いるのがよいと結論づけることができます.

各サブルーチン/関数を用いる際の注意点を示してまとめとします.

system_clockを利用する場合

  • integer(int64)を利用する.
  • 1秒あたりのカウント数は必ずsystem_clock()の引数から取得する.
  • 並列化されたプログラムの実行時間を正しく測定できない.

cpu_timeを利用する場合

  • 得られる時間は実時間ではない.
  • 並列化されたプログラムの実行時間を正しく測定できない.

date_and_timeを利用する場合

  • PGIコンパイラは標準時が標準世界時とは異なる.
  • 実行時間の計算をユーザが書く場合には,その管理が必要になる.

omp_get_wtimeを利用する場合

  • OpenMPをサポートしていないコンパイラでは利用できない.
  • モジュールomp_libuseする.
  • コンパイル時にOpenMPディレクティブを処理するコンパイルオプションを付与する.

補足:連続メモリアクセスする場合

並列プログラムの実行時間の測定で示したコードは,Fortran的にはよいコードではありませんでした12.Fortranの多次元配列は,最内(最も左側)次元でメモリアドレスが連続になるように割り付けられるので,2重のdoループの順序が悪く,各配列に対して連続的にメモリアクセスできません.コメントで指摘をいただいたので,この節ではループの順序を修正して実行時間を測定した結果をまとめます.

doループの順序を以下のように変更した以外,コンパイルオプション等の条件は全く同じです.

    do j = 1,N
    do i = 1,N
        c(i,j) = a(i,j) + b(i,j)
    end do
    end do

結果を表にまとめます.

コンパイラ 時間測定の手続 実行時間 [s]
1スレッド
実行時間 [s]
2スレッド
実行時間 [s]
4スレッド
Intel system_clock 0.113 0.069 0.048
cpu_time 0.109 0.115 0.109
date_and_time 0.114 0.060 0.029
omp_get_wtime 0.112 0.057 0.027
PGI system_clock 0.112 0.063 0.050
cpu_time 0.111 0.057 0.028
date_and_time 0.112 0.057 0.028
omp_get_wtime 0.111 0.057 0.028
gfortran system_clock 0.113 0.063 0.045
cpu_time 0.109 0.094 0.031
date_and_time 0.113 0.058 0.029
omp_get_wtime 0.113 0.057 0.028

コンパイラごとの傾向は以下の通りです.

  • Intelコンパイラは,1割ほど高速化した以外,スレッド数が変化した時のサブルーチン/関数の挙動は同じです.cpu_time()は値が変動して,まれに小さい値が得られますが,平均的には同じ挙動とみてよいでしょう.
  • PGIコンパイラはわずかに高速化する以外は,全く同じ挙動です.むしろ1スレッド実行時に遅くなっているのは大丈夫なのでしょうか.
  • gfortranの実行時間は大幅に短縮され,IntelおよびPGIコンパイラと同等になりました.cpu_time()の挙動が変わっており,1スレッド,2スレッド実行時の結果は,全コアのCPU時間が得られていると考えられますが,4スレッド実行時はdate_and_time()omp_get_wtime()と同程度の結果となりました.
  1. PGIコンパイラではCount Rate, Count Maxを取得できるが測定できない.

  2. gfortranは完全に非対応.カウントは-huge(0_int16)となり,count_rate, count_max共に0となる.

  3. Intelコンパイラ 2 3 4

  4. PGIコンパイラ 2 3 4

  5. gfortran 2 3 4

  6. ただし,cpu_time()はプログラム実行時を基準とすることも,CPU時間が0から始まることも保証していません.

  7. もちろん,ユーザ定義派生型date_and_timeを定義して,演算子をオーバーロードすればそういうことも可能ですが,ユーザ定義のモジュールを使い始めると,新しくプロジェクトを作るたびにuseしないといけませんし,異なる計算機環境へのお引っ越しに手間がかかってしまいます.

  8. コンパイラの自動並列化の方が手軽ですが,自動並列化は並列化可能で,並列化による効果があるとコンパイラが判断したときしか行われません.一方,OpenMPは並列化可能か否かに関わらず,問答無用で並列化するように指示します.

  9. 過去のある時点は決まっていないが,実行中に変化しないことが保証されている.

  10. 日本語にすると,ディレクティブ標識,条件付きコンパイル標識あたりでしょうか.

  11. コンパイラによる最適化の違いです.gfortranでも,ループを使わずにc=a+bと書き直して!$omp workshareディレクティブで並列化すると0.06秒程度まで高速化します.

  12. かなり初歩的な間違いでした.アイコンのfはfoolのfと言われても反論できません.

29
26
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?