C言語との境界で「配列記述子」を飼いならす:モダンFortranにおけるゼロコピー通信の極意
数値計算の現場において、C/C++で書かれた高速なI/OライブラリやBLAS/LAPACKの周辺ツールと、Fortranの計算コアを接続する機会は避けて通れない。しかし、この「境界領域」こそが、多くのエンジニアがセグメンテーションフォールトや性能劣化という名の深淵に足を踏み入れる場所でもある。
今回は、`ISO_C_BINDING`を利用して、Fortranの配列記述子(Descriptor)とCの生ポインタを、メモリのコピーを発生させずに安全に橋渡しする「プロの作法」を伝授する。
—
なぜ「形状の不一致」でハマるのか
Fortranの配列は、単なるメモリの塊ではない。形状情報、ストライド、下限値、そしてデータ型を含む「配列記述子(C言語から見れば構造体)」によって管理されている。一方、C言語のポインタは単なるアドレスに過ぎない。
この差異を無視して、例えばFortran側の「形状明示配列(Explicit-shape array)」をCに渡そうとすると、コンパイラは呼び出しのたびに一時的なコピーを作成し、記述子の変換を行う。大規模シミュレーションにおいて、数GBの行列をループ内でコピーしては捨てるような設計は、キャッシュミスを誘発し、ベクトル化の恩恵を全て無に帰す。
実践:CFI(C Fortran Interoperability)の最適解
最も堅牢な手法は、`TYPE(C_PTR)`を受け取り、それをFortran側の配列のポインタ(`POINTER`属性)としてポインタ代入(Pointer Assignment)することだ。これにより、メモリの連続性を維持したまま、Fortranの強力な最適化機構を適用できる。
以下のコード例は、Cから渡された生メモリをFortran側で安全にマッピングするテンプレートである。
module m_interop_util
use, intrinsic :: iso_c_binding
implicit none
contains
! C言語から受け取ったポインタを、Fortranの配列として再解釈する
subroutine map_c_array_to_fortran(c_ptr, dims, f_array)
type(c_ptr), intent(in) :: c_ptr
integer(c_size_t), intent(in) :: dims(2) ! 配列形状 (行, 列)
real(c_double), pointer, intent(out) :: f_array(:, 🙂 ! 列優先のFortran配列
! 重要なテクニック:C_F_POINTERでポインタの形状を決定する
! これにより、Fortranコンパイラは「このメモリ領域が特定の形状である」と認識し、
! SIMD最適化(ベクトル化)を最大限に適用できるようになる。
call c_f_pointer(c_ptr, f_array, shape=dims)
end subroutine map_c_array_to_fortran
end module m_interop_util
パフォーマンスを最大化する「メモリ配置」の鉄則
Fortranは「列優先(Column-major)」、Cは「行優先(Row-major)」である。この根本的な差異を理解せずに、Cから受け取ったデータをFortranのループで `f_array(i, j)` とアクセスすると、計算のたびにメモリのジャンプが発生し、キャッシュラインを無駄にする。
1. 転置の回避: 可能な限り、アルゴリズム側で「列優先」に適したループ順序(内側のループで第1添字を動かす)を強制すること。
2. アラインメントの確保: C側でメモリを確保する際、`posix_memalign`等を用いて、SIMDレジスタの幅(AVX-512なら64バイト)にアライメントを合わせること。これがズレていると、コンパイラは `vmovupd`(非アライメントロード)を生成せざるを得ず、理論性能の数割を失う。
コンパイルフラグによる最適化の制御
この設計を行った上で、コンパイラに「この配列はエイリアス(別名)を持たない」ことを明示する。これが最も泥臭いが効果的な最適化だ。
- Intel Fortran (ifort/ifx): `-qopt-report=5 -qopt-zmm-usage=high` を付与し、`!DIR$ SIMD` 指示行をループの直前に挿入せよ。
- GCC (gfortran): `-O3 -march=native -fno-trapping-math -ffast-math` を基本とし、配列の形状が固定なら `!$omp simd` でベクトル化を強制する。
特に、`ISO_C_BINDING`を介すとコンパイラがポインタの依存関係(Aliasing)を判別しにくくなる場合がある。その際は、`intent(in), contiguous` 属性を付与することで、「メモリが連続しており、他の変数と重複していない」というヒントをコンパイラに与え、ループ展開の制限を解除させること。
シニアエンジニアからの忠告
「動いたから良い」という段階で満足してはならない。`c_f_pointer` は強力だが、C側でメモリが解放された後にFortranでその配列にアクセスすれば、即座にSegmentation Faultが発生する。
- 生存期間の管理: Fortran側でマッピングした配列の寿命が、C側のメモリ確保期間を超えないよう、`module` 変数によるカプセル化でアクセス制御を行うこと。
- デバッグ: `valgrind` だけでなく、Intel Inspector 等のメモリチェッカーを必ず通し、境界外アクセスがないかをCI環境で確認するフローを組むこと。
コードの美しさは、メモリレイアウトの調和から生まれる。モダンFortranの記述子を使いこなし、C言語の柔軟性とFortranの数値計算性能を融合させることこそが、現代のハイパフォーマンスコンピューティングにおける「最適解」であると確信している。

コメント