【入門編】配列の形状引継ぎ(Assumed-shape)とインターフェースの重要性 – モダンFortran言語仕様と実践実践マスター

【脱・C言語の呪縛】なぜFortranの「Assumed-shape」が数値計算の生命線なのか?

こんにちは。かつて宇宙の深淵を覗く数値計算コードと格闘し、コンパイラの最適化フラグと一晩中対話してきた者です。

CやPythonからFortranの世界に足を踏み入れた皆さんが、最初にぶつかる壁が「配列の受け渡し」です。C言語のポインタに慣れていると、Fortranの「形状引継ぎ(Assumed-shape)」という概念が少し不思議に見えるかもしれません。しかし、これこそがFortranがスパコンで最強である理由の一つなのです。

今日は、なぜ「インターフェース」が必須であり、なぜそれがあなたのコードの速度と安定性を劇的に変えるのか、現場の視点から解説します。

1. 昔のFortranと「今のFortran」の違い

昔のFortran(固定形式時代)は、メモリの番地を直接やり取りするような危うい実装が一般的でした。しかし、モダンFortranは違います。「配列の形(次元や範囲)をコンパイラに教える」ことで、最適化の余地を最大限に引き出します。

「Assumed-shape(形状引継ぎ)」とは?

サブルーチン側で引数を受け取るとき、あえて範囲を固定せず `(:)` と書く手法です。

subroutine process_data(arr)
! この “:” が重要!
! 呼び出し元がどんなサイズの配列を渡しても、形をそのまま引き継ぐ
real(8), intent(in) :: arr(:)

! ここで size(arr) を呼べば、呼び出し元のサイズが自動的に取得できる
print , “配列のサイズは: “, size(arr)
end subroutine

これの何が嬉しいかというと、「呼び出し元がサイズを書き換えても、サブルーチン側を修正する必要がない」という点です。C言語で `int ptr` と `int size` を別々に渡してヒヤヒヤしていたあのデバッグ作業から解放されます。

2. なぜ「明示的インターフェース」が必須なのか?

Assumed-shapeを使うなら、必ず「明示的インターフェース」が必要です。これがないと、コンパイラは呼び出し元と受け取り側の整合性が取れているかチェックできません。

悪い例:インターフェースなし(昔の作法)

呼び出し側が「これは配列だ」と伝えていないと、コンパイラは単なるメモリアドレスとして扱います。もし呼び出し元とサブルーチンで次元がズレていたら……結果はご想像の通り、恐ろしいバグ(メモリ破壊)の完成です。

良い例:モジュールで包む(今の作法)

関数やサブルーチンは必ず `module` 内に定義してください。モジュールに入れておけば、`use` するだけでコンパイラが自動的にインターフェースを認識し、型や次元が合っていない場合にコンパイルエラーで止めてくれます。

module data_tools
contains
subroutine compute(vec)
real(8), intent(inout) :: vec(:)
! ここで型や次元の不一致があればコンパイル時に検知できる
vec = vec 2.0d0
end subroutine
end module

program main
use data_tools
real(8) :: my_data(10)
call compute(my_data) ! 安心!ここが整合性チェックの砦になる
end program

3. 数値計算アーキテクトからの「現場の知見」

ここで皆さんに一つ、魂の教訓を伝えます。

Fortranは「列優先(Column-major)」というメモリ配置です。C言語やPython(NumPy)とはインデックスの並び方が逆です。

  • Python/C: `data[行][列]` (メモリ上は行が連続)
  • Fortran: `data(行, 列)` (メモリ上は列が連続)

Assumed-shapeを使ってループを回す際、この「列優先」を意識したアクセスをしないと、CPUキャッシュが全く効かず、計算速度が1/100になることも珍しくありません。

最適化のポイント:
1. 連続アクセスを守れ: `do j = 1, n`(外側), `do i = 1, m`(内側)の順でループを回すこと。これでメモリを順番に舐めることができ、CPUが高速にデータを読み込めます。
2. intentを活用せよ: `intent(in)` をつけることで、コンパイラは「この配列は書き換わらない」と判断し、よりアグレッシブなレジスタ最適化を行います。

まずはここから記述してみましょう!

まずは、あなたの今の計算ルーチンを `module` の中に閉じ込めることから始めてみてください。そして、引数の配列を `(:)` に変更し、`intent` を指定する。たったこれだけで、あなたのコードは「スパコンで走らせるに足る」モダンな設計に生まれ変わります。

最初はインターフェース周りでコンパイルエラーが出るかもしれませんが、それは「計算が壊れる前にコンパイラが助けてくれている」証拠です。そのエラーは、あなたのコードを守る最強の盾なのです。

「動けばいい」コードから「正しさが保証された高速な」コードへ。Fortranの世界へようこそ。またわからないことがあれば、いつでも聞いてくださいね。

コメント

タイトルとURLをコピーしました