【入門編】INTERFACEブロックによる外部手続きの明示的インターフェース定義 – モダンFortran言語仕様と実践実践マスター

「FortranでC言語を呼ぶ」という禁断の儀式:INTERFACEブロックで安全を確保せよ

こんにちは。かつて宇宙の深淵を計算し、現在は数値計算のパフォーマンスを極限まで引き出すための基盤を作っている者です。

C言語やPythonを触ってきた皆さんが、いざ数値計算の現場に来て「Fortranで既存のライブラリを叩いてくれ」と頼まれた時、最初に直面する壁が「引数の不整合によるセグメンテーション違反(Segfault)」です。

Fortranは、良くも悪くも「コンパイラを信頼しきる」言語です。C言語のようにポインタの型が違っても(警告は出ても)動くことはある、なんて甘い世界ではありません。サイズが一つ違うだけで、メモリの海で迷子になり、プログラムは一瞬でクラッシュします。

そこで今日お伝えするのは、Fortranの守護神「INTERFACEブロック」の使い方です。これを正しく書くことは、コードの安全を確保するだけでなく、コンパイラに「これはこういう関数だから、最適化の時に好きに料理していいぞ」と教えることにも繋がります。

1. 翻訳機としてのINTERFACEブロック

想像してみてください。あなたは日本語しか話さないFortranと、英語しか話さないC言語の間に立つ通訳者です。

C言語側は「引数にはポインタ(アドレス)を渡してくれ」と言っていますが、Fortranはデフォルトで「値を渡す」という強気な姿勢で接しがちです。ここでINTERFACEブロックを定義するのは、二人の間に「契約書」を交わす行為に他なりません。

「この関数を呼ぶ時は、必ず倍精度実数(real(8))を、連続した配列として渡すこと。もしサイズが合わなかったら、呼び出し前にエラーを吐くこと」

これを明示するのがINTERFACEの役割です。

2. 実践:C言語の関数をFortranから呼び出す

例えば、C言語で書かれた非常に高速な行列積関数 `c_matmul` を呼び出したいとしましょう。

C言語側のヘッダーイメージ

// 実際にはライブラリとして提供されているもの
void c_matmul(double A, double B, double C, int n);

Fortran側のINTERFACE定義

これを通訳するためのコードがこちらです。

module interface_lib
use, intrinsic :: iso_c_binding
implicit none

! 外部のC言語関数を宣言する
interface
subroutine c_matmul(A, B, C, n) bind(c, name=”c_matmul”)
import :: c_double, c_int
! 配列のポインタを渡すために型を厳密に指定する
real(c_double), dimension(), intent(in) :: A, B
real(c_double), dimension(), intent(out) :: C
integer(c_int), value :: n
end subroutine c_matmul
end interface
end module interface_lib

3. ここがプロのこだわり:なぜ「dimension()」を使うのか?

上記のコードで `dimension()` と書いたのがポイントです。

Fortranの初心者ほど、ここで配列のサイズを決め打ちしがちです。しかし、C言語側でメモリをどう管理しているか不明な場合、“ を使って「サイズは呼び出し元が管理するから、とりあえずポインタの先を教えてくれ」と伝えるのが最も安全です。

さらに、`bind(c, name=”c_matmul”)` を付けることで、Fortran特有の「関数名の後ろにアンダースコアを付ける」といったお節介な名前修飾(Name Mangling)を防ぎ、C言語のシンボルと直接リンクさせることができます。

4. パフォーマンスの罠:列優先順位(Column-major)の呪い

ここで一つ、現場の知見を授けます。Fortranは列優先、C言語は行優先です。

もしあなたがC言語で作られた行列をFortranでそのまま受け取ると、要素が並び替わったような状態(転置された状態)で処理されてしまいます。

  • 解決策:

1. C言語側で `A[i][j]` と書いているものを、Fortranから呼ぶ時は `A[j][i]` として扱うように添字のループを調整する。
2. あるいは、INTERFACEブロックで配列の形状を工夫してラップする。

この「メモリの並び」を意識せずに大規模なシミュレーションを回すと、キャッシュミスが多発し、計算速度が10分の1以下になることも珍しくありません。「なぜか遅い」と感じたら、まずメモリ上のデータの並び(Layout)を疑ってください。

最後に:まずはコンパイルを通すことから

初めてのINTERFACE定義は、型合わせで苦戦するかもしれません。ですが、ここで`iso_c_binding`を使って型を明示することは、将来のあなた自身を「謎のセグメンテーション違反」から救うための投資です。

まずは簡単な関数から、このINTERFACEブロックを書いてみてください。コンパイラが「お、こいつは分かってるな」と、これまで以上に最適化を効かせてくれるようになるはずです。

何かうまくいかないことがあれば、またいつでも聞きに来てくださいね。皆さんの数値計算が、光速で終わることを応援しています。

コメント

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