「配列の渡し方」で計算速度が劇的に変わる?Fortran最適化の深淵へようこそ
こんにちは!元宇宙航空研究機関で、数十年もの間、スパコンの冷却ファンが唸りを上げる中でコードを磨き続けてきた「Fortranの門番」です。
CやPythonからFortranの世界に足を踏み入れた皆さん、ようこそ。Fortranは「古い」と思われがちですが、実は現代の数値計算において、コンパイラが最もその性能を極限まで引き出せる「モンスター級のツール」なんです。
今日は、皆さんが必ず直面する「配列の引数渡し(Assumed-Shape vs Explicit-Shape)」という、パフォーマンスの分水嶺についてお話しします。ここを理解するだけで、皆さんの書くコードは「動くコード」から「突き抜けるコード」へと進化します。
—
1. 「見た目」だけの違いじゃない:二つの渡し方
Fortranでサブルーチンに配列を渡すとき、大きく分けて二つの流儀があります。
昔ながらの「明示的形状 (Explicit-Shape)」
subroutine compute_explicit(a, n)
integer, intent(in) :: n
real(8), intent(in) :: a(n) ! 形状を明示的に指定
! …
end subroutine
これはC言語の `void func(double a, int n)` に近いですね。ポインタとサイズを別々に渡す、ある意味で「泥臭い」やり方です。
モダンな「形状引継ぎ (Assumed-Shape)」
subroutine compute_assumed(a)
real(8), intent(in) :: a(:) ! 形状は親から引き継ぐ
! …
end subroutine
こちらがモダンFortranの流儀です。`:` を書くだけで、コンパイラが「この配列のサイズは呼び出し元を見ればわかるから、自分で管理しておくよ」と言ってくれる、非常に賢い仕組みです。
—
2. なぜ「形状引継ぎ」で性能が落ちることがあるのか?
一見、モダンな `Assumed-Shape` の方がエレガントですよね。しかし、現場では「なぜか `Explicit-Shape` の方が速い」という現象に遭遇することがあります。
その犯人は「一時配列(Temporary Array)」の隠れた生成とインターフェースの欠如です。
配列断面(Array Sections)という罠
例えば、メインプログラムで `call compute_assumed(a(1:100:2))` のように、配列の一部だけを飛ばし飛ばし渡したとしましょう。Fortranコンパイラは、`Assumed-Shape` の引数としてこれを受け取る際、メモリ上に連続したデータが存在しないため、「メモリ上に連続した新しい配列を確保し、データをコピーしてからサブルーチンを呼び出す」という処理を裏で行うことがあります。
大規模なシミュレーションでこれをやると、コピーのオーバーヘッドだけで数ミリ秒〜数秒が溶けます。数値計算において、この「メモリのコピー」は死を意味します。
—
3. 現場の知恵:こうすれば最強になれる
では、どう設計すべきか。以下のステップを意識してみてください。
ステップ1:インターフェース(Module)を必ず使う
`Assumed-Shape` を使うなら、必ず `module` 内にサブルーチンを定義してください。これにより、コンパイラは呼び出し元とサブルーチンの両方を見渡せるようになり、コピーが不要な場合はインライン展開(コードを直接埋め込む最適化)を積極的に行ってくれます。
ステップ2:`contiguous` 属性を活用する
Fortran 2008以降、コンパイラに「この配列はメモリ上で連続しているはずだ」と教えてやるキーワードが追加されました。
subroutine compute_fast(a)
! 連続していることをコンパイラに確約させる
real(8), intent(in), contiguous :: a(:)
! これにより、最適化の幅が劇的に広がる!
end subroutine
—
4. 実践的な最適化の指針
もし皆さんが書いているコードが「極限の性能」を要求される物理シミュレーションなら、以下の設定でコンパイルしてみてください(GCC/gfortranの場合)。
最適化フラグの例
gfortran -O3 -march=native -ffast-math -fopt-info-vec-missed main.f90
- `-O3`: もちろん最高速を目指します。
- `-march=native`: 今使っているCPUの命令セット(AVX-512など)をフル活用します。
- `-fopt-info-vec-missed`: これが重要! 「なぜベクトル化(並列演算)できなかったのか」をコンパイラが教えてくれます。これを見て、配列のアクセス順序(Fortranは列優先!)を見直すのです。
—
最後に:コードは「対話」である
Fortranのコンパイラは、皆さんが書いたコードを「どうやって高速に動かそうか」と必死に考えています。`Assumed-Shape` を使ってコンパイラに情報を与え、`contiguous` でメモリの配置を保証する。これは、コンパイラという優秀な相棒に対する「信頼の証」なんです。
まずは小さなコードで、`implicit none` を忘れずに、一つずつ型を定義することから始めましょう。
分からないことがあれば、またいつでも聞きに来てください。皆さんの書く計算コードが、スパコンの上で軽やかに舞うことを心から楽しみにしています!

コメント