【実務・中級編】配列のメモリ連続アクセス(Column-Major Order)の徹底 – モダンFortran言語仕様と実践実践マスター

なぜあなたのFortranコードは「スパコン」で鈍るのか?――列優先順序の極意

多くのエンジニアが「Fortranは速い」という伝説を信じてコードを書きますが、現実には、コンパイラ任せの最適化だけでは性能の3割も引き出せていないケースがほとんどです。その主犯格こそ、メモリレイアウトに対する無頓着さ、具体的には「列優先順序(Column-Major Order)」を無視したループ構造です。

今日は、大規模シミュレーションの現場で、ノード内の演算性能を限界まで引き出すための「キャッシュ・アウェア」な実装術を伝授します。

メモリの「歩き方」を間違えてはいけない

Fortranは、メモリ上の配列を「左端のインデックスから順に」配置します。`A(i, j)`という配列があるとき、メモリ上では `A(1, 1), A(2, 1), A(3, 1), …` と並んでいるわけです。

ここで、以下のループを見てください。

! 【悪い例】キャッシュミスを誘発する典型的な書き方
do j = 1, n
do i = 1, m
a(j, i) = b(j, i) c
end do
end do

このコードでは、内側のループで `j`(行)を回していますが、メモリ上では `a(1, 1)` の次は `a(2, 1)` ではなく、巨大なジャンプをして `a(1, 2)` にアクセスします。CPUはメモリからデータを読み込む際、数バイトだけを読むのではなく「キャッシュライン(通常64バイト)」単位で一括ロードします。このコードだと、読み込んだキャッシュラインの大半を捨てながら計算することになり、メモリ・バスがボトルネックとなってCPUは常に「待ち」状態になります。

最適化された「列優先」ループの鉄則

パフォーマンスを稼ぐための鉄則は、「左側の添字を内側のループで回す」こと。これに尽きます。

! 【良い例】メモリの連続アクセスを最大限に活用
! コンパイラがSIMD命令(ベクトル化)を適用しやすくなる
do j = 1, n
do i = 1, m
a(i, j) = b(i, j) c
end do
end do

この構造であれば、`i` がインクリメントされるたびに、メモリ上の隣接するアドレスを順次読み込みます。CPUはプリフェッチャーをフル稼働させ、パイプラインを飽和させずに演算器へデータを供給し続けられます。

実践的コード:現代的なFortranにおける設計ルール

大規模解析コードでは、配列を直接扱うよりも、モジュール化された抽象インターフェースと、`contiguous`属性を活用するのが現代のセオリーです。

module vector_ops
implicit none
private
public :: scale_matrix

contains

subroutine scale_matrix(mat, factor)
! contiguous属性を付与することで、コンパイラに「メモリが連続している」と保証し、
! 最適化の制約を外す(ベクトル化の成功率が劇的に向上する)
real(8), intent(inout), contiguous :: mat(:, 🙂
real(8), intent(in) :: factor
integer :: i, j

! ループ展開とベクトル化を最大限に引き出す設計
do j = 1, size(mat, 2)
!$omp simd ! OpenMPのSIMDヒント:ベクトル化を明示的に強制する
do i = 1, size(mat, 1)
mat(i, j) = mat(i, j) factor
end do
end do
end subroutine scale_matrix
end module vector_ops

コンパイラ最適化フラグの「現場的」な落とし所

コードを書き換えるだけでなく、コンパイラの力を借りる必要があります。Intel Fortran (ifort/ifx) や gfortran を使用する場合、以下のフラグが「最低限のスタートライン」です。

  • `-O3`: 基本中の基本。ループ展開やベクトル化を最大限に行う。
  • `-march=native`: 実行マシンのCPUアーキテクチャに合わせて命令セット(AVX-512等)を最適化する。
  • `-qopt-report` (Intel): どのループがベクトル化され、どのループが失敗したかをレポート出力させる。「なぜベクトル化されなかったのか」を突き止めるために必須のツールです。

最後に:シニアからのアドバイス

「スパコンで走らせれば速い」は幻想です。キャッシュラインを意識しないコードは、最高級のプロセッサを搭載しても、メモリ帯域の制限によって「数%の性能しか出ていない」という事態を招きます。

デバッグの際は、必ずコンパイラの最適化レポートを読み、「ベクトル化失敗(Vectorization inhibited)」の理由を突き止めてください。それが、泥臭いですが、数値計算エンジニアが唯一、演算の神に近づくための道筋です。

次にコードを書くときは、まず「配列の左側の添字はどれか」を常に意識すること。この習慣一つで、あなたのシミュレーションは確実に加速します。

コメント

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