【入門編】Coarrayにおける原子操作(ATOMIC_DEFINE/REF) – モダンFortran言語仕様と実践実践マスター

「同期なんて待ってられない!」Coarrayにおける原子操作(Atomic)で並列処理を極める

こんにちは。宇宙航空の世界で長年、数千億のグリッドを叩いてきた計算アーキテクトです。

C言語やPythonを触ってきた皆さん、Fortranの世界へようこそ。数値計算の現場では「なぜ今さらFortran?」と思うかもしれませんが、「メモリを直接ぶん回して計算する」という一点において、この言語の右に出るものはありません。

今日は、Fortranの並列計算機能「Coarray」の中でも、特に泥臭くも強力な「原子操作(Atomic Operation)」についてお話しします。

1. なぜ「同期」はボトルネックになるのか?

並列処理の基本は「みんなでせーの(同期)」ですが、現場ではこれが一番の悪夢です。例えば、全プロセスから共有メモリ上の「進捗カウンタ」を更新したいとき、いちいち`SYNC ALL`(全員集合)を呼んでいたら、計算時間は同期待ちで溶けてしまいます。

そこで登場するのが `ATOMIC_DEFINE` と `ATOMIC_REF` です。これらは「計算の途中で割り込まれても、データが壊れない」ことをハードウェアレベルで保証する魔法の関数です。

2. 「看板の書き換え」をイメージしてください

原子操作を理解する一番の近道は、「無人販売所の看板」をイメージすることです。

  • 通常の変数: 誰かが書き込んでいる最中に別の人が見ると、中途半端な値(例えば「3」が「4」になる過渡期のゴミデータ)が見えてしまう可能性があります。
  • Atomic変数: 看板の書き換えは一瞬で行われ、必ず「書き換え前」か「書き換え後」のどちらかしか見えないことが保証されます。

Fortranでは、変数を `atomic_logical` や `atomic_integer` 型として定義することで、この「一瞬」を強制します。

3. 実践:カウンタを競合させずにインクリメントする

まずは、最もシンプルな「全プロセスから進捗をカウントする」コードを見てみましょう。

program atomic_counter
use iso_fortran_env
implicit none

! atomic_int は、原子操作専用の型です
integer(atomic_int_kind) :: counter[] = 0
integer :: i, val

! 各イメージ(プロセス)が勝手にカウントを増やす
do i = 1, 1000
! 自分の持っているカウンタを読み取って、+1して書き戻す
! …といきたいところですが、実はこれ単体では不十分!
! 詳細は後述しますが、まずは書き方の基本形です。
call atomic_ref(val, counter[this_image()])
call atomic_define(counter[this_image()], val + 1)
end do

sync all
if (this_image() == 1) print , “最終カウント:”, counter[1]
end program

4. ここが「泥沼」:なぜ「読み込み→書き出し」は危険なのか?

鋭い方は気づいたかもしれません。上記のコード、実は `atomic_ref` と `atomic_define` を分けて呼んでいるため、その「間」に別の処理が割り込むとカウントが飛ぶ(レースコンディション)リスクがあります。

C言語の `__sync_fetch_and_add` のような「読み書き一体型」の関数が、Fortran 2018以降では提供されています。

! 最新の規格では、atomic_fetch_add を使うのが定石です
! これなら「読み取り+加算+書き戻し」が不可分に完了します
call atomic_fetch_add(counter[1], 1, old_val)

この「アトミック関数」を使うか使わないかで、数万コア規模のスパコン上でのパフォーマンスは劇的に変わります。同期命令のオーバーヘッドを避けるため、「共有変数は極力アトミックで叩く」のが、現代の高速シミュレーションの鉄則です。

5. 若手エンジニアへのアドバイス:コンパイラの最適化フラグ

Fortranで並列コードを書く際、コンパイラの最適化フラグ(`-O3`, `-Ofast`など)を使うと、コンパイラが「この変数は他に参照されないはずだ」と勝手に判断してキャッシュに入れてしまうことがあります。

`atomic` 指定された変数は大丈夫ですが、そうでない共有変数は必ず `volatile` 属性をつけるか、適切に `sync` 境界を設けてください。これを怠ると、「手元のPCでは動くのに、スパコンに投げると結果が化ける」という、夜も眠れないデバッグ地獄が待っています。

まとめ:まずは小さく始めてみよう

1. `integer(atomic_int_kind)` を使う。
2. `atomic_ref` と `atomic_define` で「読み書き」の重さを知る。
3. 最終的には `atomic_fetch_add` のような専用関数で、競合をハードウェアに委ねる。

最初は難しく感じるかもしれませんが、この「メモリの振る舞い」を意識する感覚こそが、Fortranエンジニアとしての最大の武器になります。

さあ、まずはこのコードを `gfortran -fcoarray=single` (まずは1プロセスで) から動かして、徐々にマルチプロセス環境へと広げてみてください。何か詰まったら、いつでも聞いてくださいね。応援しています!

コメント

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