Edited at
FortranDay 24

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


概要

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コンパイラ 



  4. PGIコンパイラ 



  5. gfortran 



  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と言われても反論できません.