【入門編】C言語連携における配列の形状(Shape)情報の伝達 – モダンFortran言語仕様と実践実践マスター

異文化交流の落とし穴:Fortranの「列優先」とC言語の「行優先」を仲直りさせる方法

こんにちは。かつて宇宙の深淵を計算で追いかけていた、数値計算アーキテクトです。

C言語やPython(NumPy)の世界に慣れ親しんだ皆さんが、いざFortranの門を叩くと必ず直面する「壁」があります。それは「メモリの中の配列の並び順」という、極めて物理的かつ泥臭い問題です。

今日は、Fortranで計算した爆速の行列を、C言語やPythonから「正しく」受け取るための極意を伝授します。ここを間違えると、計算結果が「謎の転置行列」になって返ってくるという悪夢を見ることになりますよ。

1. 「列優先(Column-major)」というFortranのアイデンティティ

C言語やPython(C API)では、配列のインデックスを `A[row][col]` と書きますよね。これはメモリ上で「行」が先に並ぶ「行優先(Row-major)」です。

一方で、Fortranは「列優先(Column-major)」です。`A(row, col)` と書いたとき、メモリ上では `(1,1), (2,1), (3,1)…` という順でデータが詰まっています。

この違いを無視してC言語側で `double` としてポインタを渡すと、C側からは「列」が「行」に見えてしまい、データがぐちゃぐちゃに解釈されてしまいます。

2. ISO_C_BINDINGで「形」を正しく伝える

現代のFortran(モダンFortran)には、C言語と安全に会話するための強力な武器 `iso_c_binding` モジュールがあります。これを使わない手はありません。

以下のコードを見てください。Fortran側で「この配列はこういう形だよ」と明示する設計です。

module matrix_mod
use, intrinsic :: iso_c_binding
implicit none

contains

! C言語から呼ばれる関数:配列のポインタとサイズを渡す
subroutine process_matrix(arr, rows, cols) bind(c, name=”process_matrix”)
integer(c_int), value, intent(in) :: rows, cols
real(c_double), intent(inout) :: arr(rows, cols) ! Fortranは列優先で解釈する

! ここで計算処理を行う
! Fortranのループは「左の添字(行)から回す」のが鉄則!
! そうすることでメモリの連続アクセスが保証され、キャッシュが劇的に効く
integer :: i, j
do j = 1, cols
do i = 1, rows
arr(i, j) = arr(i, j) 2.0_c_double
end do
end do
end subroutine
end module

3. C言語側での受け取り方:転置の罠を回避する

C言語側でこの関数を叩くとき、一番簡単な解決策は「C側でもFortranのメモリ配置(列優先)を意識したデータ構造を作る」ことです。

例えば、C言語で `double arr[COLS][ROWS]` と定義すると、C言語のメモリレイアウト上は「行優先」として扱われますが、Fortran側が期待する「列優先」の並び順と偶然一致します。

// C言語側
include

// Fortranの関数を宣言
void process_matrix(double arr, int rows, int cols);

int main() {
int rows = 3, cols = 2;
// Fortranの列優先に合わせるため、あえて [cols][rows] で確保する戦略
double data[2][3] = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};

process_matrix(&data[0][0], &rows, &cols);

return 0;
}

4. 最適化の現場から:ここだけは守れ!

最後に、私が長年スパコンのチューニングをしてきて学んだ「血の滲む教訓」を2つだけ伝えます。

1. ループの順序は「列、行」の順に回す
Fortranで `do i = 1, rows` を外側(左)のループに置くと、メモリアクセスが連続的になります。これを逆にすると、CPUのキャッシュミスが連発し、計算速度が10倍以上落ちることもあります。「インデックスの左側を速く回す」これだけは体に染み込ませてください。
2. `intent(inout)` を過信しない
C言語からポインタを渡す際、そのメモリ領域が本当に確保されているか、Fortran側の宣言とサイズが一致しているかは、コンパイラは(基本的には)チェックしてくれません。必ずC側で `sizeof` を使い、Fortran側の配列サイズと一致するように細心の注意を払ってください。

最後に:怖がる必要はありません

Fortranは古い言語だと言われますが、こと「行列演算の効率」においては、未だに現代言語を凌駕するシンプルさとパワーを持っています。

まずは小さな2次元配列からでいいので、C言語とFortranの間で値をやり取りしてみてください。「あ、メモリの並び順さえ合わせれば、意外と仲良くできるんだ」と気づいたとき、あなたはもう立派な数値計算エンジニアの入り口に立っています。

もしデバッグで詰まったら、それはコンパイラが「お前のメモリ配置、効率悪いぞ!」と怒っているサインです。そのサインこそが、高速計算への近道ですよ。頑張ってくださいね!

コメント

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