概要
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()
を用いてプログラムの実行時間を測定するには,以下の手順と実装が考えられます.
- 測定したい処理を実行する直前で
system_clock()
を呼ぶ.このとき,1秒あたりのカウント数も取得しておく. - 測定したい処理が終了したら
system_clock()
を呼ぶ. - 終了後のカウントと実行直前のカウントの差を計算し,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()
を用いて以下の手順で実行時間を測定します.
- 測定したい処理を実行する直前で
cpu_time()
を呼ぶ. - 測定したい処理が終了したら
cpu_time()
を呼ぶ. - 終了後の時間と実行直前の時間の差を計算する.
秒単位で取得できるので,とても楽です.テストプログラムを下のように実装しました.
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が働いていない時間(sleep
やread*
による標準入力待ちなど)を除いた時間が得られます.
これがよい挙動かどうかは測定したい処理によって異なるでしょう.全てのコンパイラで挙動が一致していれば問題ありませんが,調査した範囲では,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()
を用いて実行前後の年月日時分秒ミリ秒を得ると,経過時間を計算できます.
- 測定したい処理を実行する直前で
date_and_time(values=...)
を呼ぶ. - 測定したい処理が終了したら
date_and_time(values=...)
を呼ぶ. - 終了後の年月日時分秒ミリ秒と実行直前の年月日時分秒ミリ秒から経過時間を計算する.
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_lib
をuse
する必要があります.
サブルーチン | 機能 |
---|---|
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_time
やomp_get_wtime
と同様の結果が得られています.PGIコンパイラの実装がアレなだけでは・・・ -
system_clock
による測定結果はスレッド数の増加とともに減少しますが,2スレッド→4スレッドでの減少は他の手続による結果と比較して少なく,実時間よりも大きな値が得られます.こうなる理由はわかりません. -
date_and_time
は概ねスレッド数に反比例して実行時間が減少します. -
omp_get_wtime
はdate_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_lib
をuse
する. - コンパイル時に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()
と同程度の結果となりました.
-
PGIコンパイラではCount Rate, Count Maxを取得できるが測定できない. ↩
-
gfortranは完全に非対応.カウントは
-huge(0_int16)
となり,count_rate
,count_max
共に0となる. ↩ -
Intelコンパイラ ↩
-
PGIコンパイラ ↩
-
gfortran ↩
-
ただし,
cpu_time()
はプログラム実行時を基準とすることも,CPU時間が0から始まることも保証していません. ↩ -
もちろん,ユーザ定義派生型
date_and_time
を定義して,演算子をオーバーロードすればそういうことも可能ですが,ユーザ定義のモジュールを使い始めると,新しくプロジェクトを作るたびにuseしないといけませんし,異なる計算機環境へのお引っ越しに手間がかかってしまいます. ↩ -
コンパイラの自動並列化の方が手軽ですが,自動並列化は並列化可能で,並列化による効果があるとコンパイラが判断したときしか行われません.一方,OpenMPは並列化可能か否かに関わらず,問答無用で並列化するように指示します. ↩
-
過去のある時点は決まっていないが,実行中に変化しないことが保証されている. ↩
-
日本語にすると,ディレクティブ標識,条件付きコンパイル標識あたりでしょうか. ↩
-
コンパイラによる最適化の違いです.gfortranでも,ループを使わずに
c=a+b
と書き直して!$omp workshare
ディレクティブで並列化すると0.06秒程度まで高速化します. ↩ -
かなり初歩的な間違いでした.アイコンのfはfoolのfと言われても反論できません. ↩