NVIDIA HPC SDK nvfortranの挙動を他のコンパイラと比較する
概要
これまでに,Fortranの基本的な機能を色々と調べてきたので,PGI Fortranからnvfortranに代わり,挙動がどう変化したのかを調査しました.
重要なのは,下記の点くらいです.
- 基本的な挙動はPGI Fortranと同じ
- 4倍精度実数は相変わらずサポートされていない
- 配列構成子内での明示的な型変換,allocatableな配列への一括代入時の挙動が,どのコンパイラとも異なる
- 派生型の機能は,他のコンパイラに少々及ばない
これまでPGI Fortranを使う上で気をつけていたことをそのまま気をつけていれば1,特に大きな問題は生じないと思われます.
使用環境
コンパイラ | バージョン |
---|---|
intel Parallel Studio XE Composer Edition for Fortran | 17.0.4.210 |
PGI Visual Fortran for Windows | 18.7 |
gfortran | 7.3.0 |
nvfortran | 20.5 |
整数・実数・複素数型関係
ほとんどの挙動は,Fortranにおける整数型・実数型・複素数型変数の宣言方法のPGI Fortranと同じです.
また,下記についても,PGI Fortranと同じ挙動でした.
Intel Fortranやgfortranとの違いは,下記小節にまとめています.
2進数,8進数,16進数リテラルの表示の差異
Fortranで2,8,16進数のリテラルを扱うには,基数を表す記号の後ろに' '
あるいは" "
で囲んだ数値を配置します.
基数を表す記号は,2進数はB
,8進数はO
,16進数ではZ
で,まとめてBOZ
とよばれています.
print *,B'11',O'77',Z'FF'
2,8,16進数のリテラルを画面表示する際,Intel Fortranおよびgfortranは,10進数に変換して表示します.
3 63 255
一方で,nvfortranは16進数で表示されます.
00000003 0000003F 000000FF
10進数で表示するには,明示的に整数型に型変換する必要があります.
print *,int(B'11'),int(O'77'),int(Z'FF')
3 63 255
4倍精度実数の未サポート
PGI Fortranは4倍精度実数をサポートしていませんでしたが,nvfortranも同様です.
selected_real_kind
で倍精度実数の範囲を超える桁数や指数の範囲を指定すると,桁数や指数の要件を満たすことができない場合に返されるステータス-3
が返ってきます.
print *,selected_real_kind(p=33,r=4931)
-3
4倍精度実数の指数記号であるq
を使うとコンパイルエラーになりますし,iso_fortran_env
内では,real128
は-1
として定義されています.
配列関係
ほとんどの挙動は,Fortranにおける配列の宣言方法と関連機能のPGI Fortranと同じですが,PGI FortranやIntel Fortran, gfortranとも異なる挙動をする処理もあります.
配列構成子内での明示的な型変換がエラーになる
配列の要素に異なる値を一括で代入したい場合には,配列構成子[]
が利用できます.[
と]
の間に数値リテラルや変数(配列を含む)を記述することで,配列リテラルを構成します.
program main
use,intrinsic :: iso_fortran_env
implicit none
real(real32) :: a(5)
real(real32) :: b(5)
real(real32) :: c(5)
a(:) = [0.1, 0.2, 0.3, 0.4, 0.5]
b(:) = [1, 2, 3, 4, 5] !暗黙の型変換
c(:) = [real(real32) :: b(1), a(2:4), 0] !明示的な型変換
end program main
配列構成子内で数値リテラルのみを用いる場合,暗黙の型変換によって適切な型に変換されます.
一方で,配列構成子に変数を用いる場合,変数の型が混在していると,Intel Fortranとgfortranではコンパイルエラーになります(PGI fortranおよびnvfortranではエラーになりませんでした).そのような場合には,配列構成子[]
内の一番左に型名 ::
を置いて,明示的に型変換を行う必要があるのですが,nvfotranでは明示的な型変換を行うとエラーが出ます.
PGI Fortranでは明示的な型変換はしてもしなくても問題なかったのですが,nvfortranでは明示的な型変換がエラーになるので,コンパイラ間の移行作業の際は,気を付ける必要がありそうです.
配列定数の暗黙形状宣言には(未だ)未対応
配列の定数を宣言すると,配列の要素数や形状はあらかじめ決まります.Fortran 2008では,この性質を利用して,配列の定数を宣言する際にその要素数の記述を簡略化できます.暗黙の形状配列宣言は,配列要素番号の下限と上限を指定する形式に似ていますが,上限を*として,上限は右辺から決められるようにします.
program main
use,intrinsic :: iso_fortran_env
implicit none
integer,parameter :: a(1:*) = [1,2,3,4]
integer,parameter :: b(1:*,-1:*) = reshape([1,2,3,4,5,6],[3,2])
print *,size(a),shape(a) ! 4 4
print *,a(:) ! 1 2 3 4
print *,size(b),shape(b) ! 6 3 2
print *,lbound(b),ubound(b) ! 1 -1 3 0
print *,b(:,:) ! 1 2 3 4 5 6
end program main
(PGI Fortranと同じく)nvfortranは,この配列の暗黙形状宣言には対応していません.
異なる形状の配列同士の一括代入時の挙動
配列の要素番号の代わりに(:)
を用いると,配列の全要素に対する処理であることを明記できます.
program main
use,intrinsic :: iso_fortran_env
implicit none
real(real32) :: a(5)
real(real32) :: b(5)
real(real32) :: c(5)
a(:) = 1.0 !a(1)~a(5)に1.0を代入
b(:) = 2.0 !b(1)~b(5)に2.0を代入
c(:) = 0.0 !c(1)~c(5)に0.0を代入
c(:) = a(:) + b(:) !配列の各要素同士の和を計算
print *,c(:)
! 3.000000 3.000000 3.000000 3.000000 3.000000
end program main
配列形状を静的に(宣言時に)決定した場合は,(:)
を使って異なるサイズの配列同士の演算を行うことはできませんが,allocatable
な配列の場合は,(:)
を使って異なるサイズの配列同士の演算を行うことができます.
program main
use,intrinsic :: iso_fortran_env
implicit none
integer(int32),allocatable :: a(:)
integer(int32) :: b(5)
integer(int32) :: c(3)
allocate(a(5),source=1)
b(:) = 2
c(:) = 3
print *,a(:)+c(:) ! 3 3 3 1 1
print *,b(:)+c(:) ! <- コンパイルエラー
end program main
2次元配列に対して,(:)
を使って異なるサイズの配列同士の演算を行うと,PGI Fortranとnvfotranは挙動が怪しくなります.
下の例では,[3,2]の配列と[2,2]の配列の足し算をしています.
program main
use,intrinsic :: iso_fortran_env
implicit none
integer(int32),allocatable :: b(:,:)
integer(int32) :: c(2,2) = reshape([4,3,2,1], [2,2])
allocate(b,source=reshape([1,2,3,4,5,6], [3,2]))
print *,b(:,1)
print *,b(:,2)
print *
b(:,:) = c(:,:)
print *,b(:,1)
print *,b(:,2)
deallocate(b)
end program main
Intel fortranとgfortranは,b(:,:) = c(:,:)
がb(1:2,1:2) = c(1:2,1:2)
と展開されて代入が行われています.
1 2 3
4 5 6
4 3 3
2 1 6
PGI Fortranの挙動は,よくわかりません.
1 2 3
4 5 6
4 3 2
2 1 1
nvfortranの挙動もよくわかりません.
1 2 3
4 5 6
4 3 2
2 1 0
Fortran規格でどう定義されているかを調べなければなんとも言えませんが,コンパイラを移行した際に,思わぬ挙動に遭遇する可能性があるので,こうなることは知っておいて損はないでしょう.
文字型変数,文字列関係
挙動は,Fortranにおける文字型変数の宣言方法と関連機能と同じでした.また,整数の文字列への変換(Fortranで整数を文字列に変換する)も,問題なく動作しました.
文字列に関して,特に問題のある挙動は見られません.コンパイラ間の可搬性という意味では,下記2点に気を付ける事になります.
- エスケープ文字の取り扱い
- Intel Fortran, gfortranはバックスラッシュを通常の文字として扱うが,PGI Fortranおよびnvfortranは,エスケープ文字として扱う.
- バックスラッシュを通常の文字として扱うには,コンパイルオプション
-Mnobackslash
を付与する必要があるので,標準のコンパイルオプションとして付ける癖を付けた方がよい.
- 文字種別が4の文字の取り扱い
- PGI Fortranおよびnvfortranは,Unicode向けの利用を想定した文字種別(
kind=4
)が利用できない. - 文字種別を確認するための定数
character_kinds
が定義されていない.
- PGI Fortranおよびnvfortranは,Unicode向けの利用を想定した文字種別(
ユーザ定義派生型関係
ほとんどの挙動は,Fortranにおける派生型の基本的な使い方と同じです.
再帰型の未サポート
いわゆる構造体に相当するユーザ定義派生型は,自身の型の変数を成分に持つことはできません.ただし,pointer
属性およびallocatable
属性を付与した派生型変数であれば,同一派生型の成分として持つことができます.
program main
use,intrinsic :: iso_fortran_env
implicit none
type :: vector2d
real(real32) :: x
real(real32) :: y
type(vector2d),pointer :: ptr ! ポインタ変数であれば成分として宣言できる.
type(vector2d),allocatable :: vec ! allocatable属性があれば成分として宣言できる.
end type vector2d
end program main
このように,allocatable
属性を付与した自身の型の変数を成分としてもつ派生型を,再帰型(recursive type)とよびます.
PGI Fortranと同様に,nvfortranはこの再帰型には対応していません.pointer
属性は,昔からメモリリークなどの問題の原因となっていたので,自動で解放されるallocatable
属性を利用できる方が,安全性という意味で非常に好ましいと考えられます.早めに対応してもらいたいところです.
ユーザ定義派生型IO
ユーザ定義派生型IOについては,Fortranのユーザ定義派生型の出力用サブルーチンの書き方やFortranで区間演算のプログラムがコンパイルできません.
その理由は,モジュール内をprivate
とし,モジュール外部へ変数やサブルーチンを公開するための記述public write (formatted)
に対応していないためです.
対策は2通りあります.Fortranのユーザ定義派生型の出力用サブルーチンの書き方のtuple2
型を例に説明すると,
- モジュール内全てを
public
とする.
その場合も,interface
には修正が必要です.
interface write(formatted)
! procedure printTuple2 !Intel Fortran およびgfortran
module procedure printTuple2 ! nvfortran
end interfac
- ユーザ定義派生型IO用のサブルーチンを,型束縛手続とする.
型束縛手続として型に含め,generic
でIO用サブルーチンとして関連付けます.モジュール内部をpublic
にしたくない場合には,これが最善の対策といえるでしょう.
type,public :: tuple2
real(8),public :: x
real(8),public :: y
contains
procedure, public, pass :: printTuple2
generic :: write(formatted)=>printTuple2
end type tuple2
FortranとCの相互運用関係
FortranとC言語の相互運用について,以前に掲載した記事ではPGI Fortranを取り扱っていませんでしたが,nvfortranとnvc++を使って実行したところ,挙動は同じでした.
ただし,sprintf_s
は利用できないので,sprintf
に書き換えています.
時間測定関係
Fortranにおける実行時間の測定と比較して,サブルーチンdate_and_time
の挙動に1点修正がありました.また,時間測定に関して,(変わっていないからこそ)気をつけなければならない挙動について記しておきます.
date_and_time
のタイムゾーン
サブルーチンdate_and_time
では,サブルーチン呼出し時点の年月日と時刻(時,分,秒,ミリ秒)が得られます.このとき,PGI Fortranでは,標準時からの時差が0になっており,OSのタイムゾーンを標準時としているような挙動でした.
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
20200613
133503.719
+0000 ! 標準時からの時差
2020 6 13 0 13 35
3 719
nvfortranでは,Intel Fortranやgfortranと同じく,協定世界時を標準時とするように変わっています2.
20200613
133517.124
+0900 ! 標準時からの時差
2020 6 13 540 13 35
17 124
cpu_time
の挙動
cpu_time
の挙動は,PGI Fortranと同じく,Intel Fortran, gfortranのそれとは異なり,注意が必要です.
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
cpu_time
でsleep()
前後の時間を測定すると,Intel Fortran, gfortranでは前後の時間が等しくなりますが,nvfortranでは前後の時間が等しくなりません.
cpu_time()
は,その名前の通りCPU時間を取得するサブルーチンです.sleep()
はCPUを(正確にはプロセス)を停止するので,Intel Fortran, gfortranではsleep()
前後の時間が等しくなりました.nvfortranは,どうも実時間が得られているようです.
また,処理を並列化した場合,Intel Fortran, gfortranではcpu_time()
によって全CPU時間の合計が得られるので,測定された時間は使用するコア数によって変化しません.一方で,nvfortranは,処理を並列化して実行時間が短縮されると,測定された実行時間も短くなります.
コンパイラに依存せず,実時間を測定したいという要望に対しては,date_and_time()
かomp_get_wtime()
の使用が推奨されます.だたし,date_and_time()
を用いる場合は,年月日と時刻の情報から実行時間を計算する処理を記述する必要があります.
まとめ
現在(バージョン20.5)の時点では,nvfortranは名前こそ変わっていますが,PGI Fortranと同じものと見なせるでしょう.
NVIDIA HPC SDKと名前を変えたことで何かが変わったということはなさそうです.ただし,コンパイルオプションには,cc80
が既に追加されていて,対応が早くなったように感じます.
GTC 2020の講演では,HPC GPUプログラミングモデルはCUDA Fortran,OpenACCからISO FORTRANになってProgrammabilityが大幅に向上するようなことが言われていたので,今後はFortran 2008の機能も随時追加されていくと期待しています.
Author And Source
この問題について(NVIDIA HPC SDK nvfortranの挙動を他のコンパイラと比較する), 我々は、より多くの情報をここで見つけました https://qiita.com/implicit_none/items/bd63a255e2f93cadc940著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .