Fortranの「列優先」に殺されないために:CPUキャッシュを味方につけるメモリ配置の極意
こんにちは。元宇宙航空研究機関で数値計算のアーキテクチャを設計していた者です。
C言語やPythonからFortranの世界に飛び込んできた皆さんが、最初にぶつかる壁であり、かつ「Fortranの速さの秘密」そのものに直結するのが、多次元配列のメモリ配置です。
巷の入門書では「Fortranは列優先(Column-major)だ」と一言で片付けられがちですが、これがいかに現場のパフォーマンスを左右するか。今日は、スパコンの計算時間を半分、あるいは10分の1に削り出すための「メモリの作法」について、泥臭い知見を共有しましょう。
—
1. なぜ「列優先」なのか?:配列を「本棚」に例える
まず、メモリの構造をイメージしてください。コンピュータのメモリは、本質的に「1列に並んだ長い棚」です。
- C言語やPython(行優先): 本を「1行ずつ」横に並べていく感覚です。
- Fortran(列優先): 本を「1列ずつ」縦に積み上げていく感覚です。
Fortranで `A(3, 3)` という配列を定義したとき、メモリ上では以下のように並びます。
Fortranのメモリ配置:
A(1,1) -> A(2,1) -> A(3,1) -> A(1,2) -> A(2,2) -> A(3,2) -> …
ここが重要です。Fortranで多次元配列を扱うとき、左側の添字が最も速く変化するようにループを回すのが鉄則です。これが逆になると、CPUはメモリ上の飛び飛びの場所を行き来する羽目になり、キャッシュミス(CPUが欲しいデータが高速なキャッシュメモリに載っていない状態)が連発します。
—
2. 実践:ループ順序の最適化(Loop Interchange)
では、具体的にどう書くべきか。悪い例と良い例を見てみましょう。
悪い書き方(キャッシュ効率最悪)
! 列優先のFortranで、行方向に先に回すと悲劇が起こる
do j = 1, N
do i = 1, M
A(i, j) = A(i, j) 2.0d0
end do
end do
これでも動きますが、計算機は悲鳴を上げています。`j`(外側ループ)のインクリメントごとにメモリ上の遠く離れた場所へジャンプしているからです。
良い書き方(Fortranの作法)
! 左側の添字(i)を内側に置く
do j = 1, N
do i = 1, M
A(i, j) = A(i, j) 2.0d0
end do
end do
…お気づきでしょうか? 実は上の例、Fortranのメモリ配置を理解していれば、最も内側のループで `i` が変化するように書くのが正解なんです。
「え、じゃあ今のコードはどっちなの?」と思った方、素晴らしい着眼点です。Fortranでは `A(i, j)` と書いたとき、`i` が連続して並んでいるので、`i` を内側のループにするのが「キャッシュを殺さない」書き方なのです。
—
3. コンパイラは魔法使いではない:最適化フラグの現実
若手エンジニアからよく聞くのが「今のコンパイラは賢いから、最適化フラグを付ければ順序なんて気にしなくていいのでは?」という疑問です。
確かに `-O3` や `-Ofast` をつければ、コンパイラが自動でループを入れ替えてくれるケースもあります。しかし、複雑なポインタ演算やモジュールを跨ぐ計算、あるいはインライン展開が効かない巨大なサブルーチンの中では、コンパイラは手出しできません。
皆さんが書いたコードの「メモリの歩き方」が悪いと、どれだけ最新のCPUを使っても、その性能の数%しか引き出せないのです。
推奨ビルド設定(gfortranの例)
まずはコンパイラに「警告を出しなさい」と命じましょう。
最適化と、コードの不備を指摘させるフラグ
gfortran -O3 -march=native -Wall -Wextra -fopt-info-vec-optimized main.f90 -o simulation
`-fopt-info-vec-optimized` を付けると、コンパイラが「ループをベクトル化(高速化)できたか」を教えてくれます。ここで何も表示されないなら、あなたのループ順序がキャッシュ効率を阻害している可能性大です。
—
4. 最後に:現場で生き残るために
数値計算の現場で重要なのは、「コンピュータの視点でメモリを眺めること」です。
1. 添字の左側を内側ループにする。
2. 配列の走査はメモリの物理順序に合わせる。
3. 迷ったら `gfortran` や `ifx` の最適化レポートを信じる。
最初は面倒に感じるかもしれませんが、これを意識するだけで、数日かかっていたシミュレーションが数時間で終わるようになる。これがFortranという言語の、そして数値計算の醍醐味です。
さあ、皆さんのコードを開いて、ループの `i` と `j` を見直してみてください。きっと、CPUが感謝の悲鳴を上げているはずですよ。また次のトピックで、より深い「泥沼」の話をしましょう。応援しています!

コメント