宇宙航空の現場で何十年もスパコンの冷却ファンが唸る音を聞きながら、数式とコードの狭間で戦ってきた者として、一つだけ断言できることがあります。「Fortranの真髄は、メモリをどう『なでるか』にある」ということです。
CやPythonから来た皆さんは、おそらく「行列は行優先(Row-major)」という感覚が染み付いているはずです。しかし、Fortranは違います。Fortranは「列優先(Column-major)」という、ある種の硬派な美学を持っています。
今日は、なぜこの「順序」が計算速度を天と地ほど分けるのか、その泥臭い真実をお話ししましょう。
—
1. なぜ「列優先」なのか?:メモリを撫でる順番の話
皆さんが本棚から本を取り出すときを想像してください。Fortranにとって、メモリ上の配列は「縦に並んだ本棚」です。
- Fortran(列優先): 1列目を上から下へ、次に2列目を上から下へ…と読み進めます。
- C言語(行優先): 1行目を左から右へ、次に2行目を左から右へ…と読み進めます。
もし皆さんが、列優先のFortranで「行方向」にループを回してしまったらどうなるか。それは、本棚の「1段目の1冊目」を取ったあと、わざわざ隣の本棚へ移動し、また次の本棚へ…と、教室中を走り回るようなものです。これではCPUのキャッシュ(高速な作業机)が全く機能しません。
悪い書き方の例(キャッシュミスを誘発する)
! 非効率:列優先のFortranで、行方向にアクセスしている
do j = 1, n
do i = 1, m
a(i, j) = a(i, j) + 1.0 ! ここはOK。でもループの入れ替えが逆だと…?
end do
end do
もし、これの `i` と `j` を逆にして内側ループで `a(j, i)` と書くと、メモリ上では飛躍的に離れた場所を交互に叩くことになります。これを「ストライド(Stride)が大きい」と呼びます。ストライドが大きいアクセスは、CPUのプリフェッチャー(先読み機能)を殺し、計算速度を10倍、ときには100倍遅くします。
—
2. 実践!キャッシュを味方につける書き方
では、どう書くのが正解か。答えはシンプルです。「一番左の添字を、一番内側のループで回す」ことです。
! 効率的:列優先を活かしたメモリ連続アクセス
do j = 1, n
do i = 1, m
! a(i, j) の i が内側ループで動くので、
! メモリ上の隣接データを順に触ることになる
a(i, j) = a(i, j) 2.0
end do
end do
この書き方なら、CPUは「次はここが必要だな」と予測して、メモリからキャッシュライン(通常64バイト)分を事前に読み込んでくれます。この「行列の右側のインデックスを外側に、左側を内側に」という鉄則を指先が覚えるまで、このコードを書いてみてください。
—
3. コンパイラの最適化フラグと「神頼み」の限界
現代のコンパイラ(gfortranやifort/ifx)は非常に賢いです。しかし、彼らも魔法使いではありません。
皆さんがプログラムをビルドする際、単に `gfortran main.f90` と打っているなら、それはF1カーに軽自動車のエンジンを積んでいるようなものです。最低限、以下のフラグを検討してください。
現場でよく使う最適化フラグの例
gfortran -O3 -march=native -ffast-math -o simulation.exe main.f90
- `-O3`: 最適化のフルコースです。ループの展開(Unrolling)など、泥臭い最適化をコンパイラに任せます。
- `-march=native`: あなたのCPUの機能を最大限引き出します。
- `-ffast-math`: 浮動小数点の計算順序を少し入れ替えてでも速度を優先します(※結果が微量に変わる可能性があるため、厳密な科学計算では注意が必要ですが、現場では常用します)。
—
4. 若手エンジニアへのアドバイス
「なぜこんな古い言語を使うのか」と疑問に思うこともあるでしょう。しかし、現代のスパコンやHPC(高性能計算)の最前線において、Fortranのメモリ配置の明快さは、最適化のしやすさという点でPythonやC++にはない武器になります。
まずは、自分の書いたコードが「メモリを連続的に撫でているか」を意識してみてください。もし計算が終わらないときは、`do` ループの順番を入れ替えるだけで、コーヒーを淹れている間に計算が終わるかもしれません。
Fortranは、皆さんが書いた数式を、ハードウェアの限界速度で現実に変えてくれる最高のツールです。ぜひ、この「メモリの質感」を楽しんでください。応援しています!

コメント