TL

NUMAとメモリアクセス局所性

マルチソケットで性能が伸びない理由が腑に落ちます。ノード間アクセスの非対称コスト、メモリポリシー、自動NUMAバランシングを設計判断の軸で押さえられます。

応用NUMAメモリスケジューラ局所性Linux最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.NUMAではCPUとメモリがノードに分かれ、自ノードより遠いノードへのアクセスは帯域が低く遅延が大きい非対称コストを持つ。
  • 2.メモリポリシー(local/bind/interleave)でページの配置先ノードを制御し、局所性を取るか帯域を均すかを使い分ける。
  • 3.自動NUMAバランシングはアクセス傾向を観測してページとタスクを同じノードへ寄せるが、移動コストとのトレードオフがある。

なぜアクセスコストが一様でなくなるのか

UMA(Uniform Memory Access)では、すべての CPU が単一のメモリバスを共有し、どのアドレスへも同じコストで届きます。素直な設計ですが、コア数が増えると共有バスとメモリコントローラがボトルネックになり、コアを足しても帯域が頭打ちになります。

NUMA(Non-Uniform Memory Access)はこの限界を、メモリコントローラを分散させて解きます。CPU コア群とその近傍のメモリ(DRAM)を1つのノードにまとめ、ノード同士を高速インターコネクト(Intel UPI、AMD Infinity Fabric など)でつなぎます。各コアは自ノードのメモリには内蔵コントローラ経由で直結しますが、他ノードのメモリへはインターコネクトを1ホップ以上越えて到達します。

[Node 0]                         [Node 1]
 CPU0 CPU1 ── 内蔵MC ── DRAM      DRAM ── 内蔵MC ── CPU2 CPU3
   |                                              |
   +============ インターコネクト ================+
       (ノード間アクセスはここを通る=余分な遅延と帯域制約)

結果としてアクセスコストは非対称になります。同じ命令・同じデータ量でも、対象ページがどのノードにあるかで遅延と実効帯域が変わる。これが「メモリアクセスが Non-Uniform」の意味です。

距離はSLITテーブルで数値化される

ファームウェアは ACPI の SLIT(System Locality Information Table)でノード間の相対距離を公開します。慣例上、自ノードは 10、リモートはおおむね2倍前後(例 20〜21)で表現されます。Linux からは numactl --hardware で各ノードのメモリ量と距離行列を確認でき、スケジューラやアロケータはこの距離を配置判断の入力に使います。

ローカルとリモートの差は何に効くか

リモートアクセスのペナルティは「遅延」と「帯域」の2軸で現れます。遅延はインターコネクトのホップ分が上乗せされ、典型的にローカルの 1.5〜2 倍程度になります。帯域はインターコネクトの容量に律速され、さらに競合が効きます。全コアが特定の1ノードに集中アクセスすると、そのノードのメモリコントローラとインターコネクトが飽和し、ローカル相当の理論帯域は出せません。

観点ローカルアクセスリモートアクセス
経路内蔵メモリコントローラへ直結インターコネクトを1ホップ以上経由
相対遅延基準(SLITで10)おおむね1.5〜2倍(SLITで21など)
実効帯域ノードのMC帯域をフルに使えるインターコネクト容量と競合に律速
スケール特性ノードを足すほど総帯域が増える片寄ると総帯域が増えない

設計上の含意は明快です。スレッドと、そのスレッドが触るデータを同じノードへ寄せれば、総帯域はノード数に比例して伸びます。逆に配置が無頓着だと、コアを増やしてもインターコネクト越しの競合で性能が飽和する——これが NUMA を意識しない並列プログラムでよく起きる「スケールしない」現象の正体です。

メモリポリシー:ページをどのノードに置くか

物理ページがどのノードから割り当てられるかは、仮想記憶のページフォルト時に確定します。Linux はこの割当先を選ぶ規則をメモリポリシーとして制御でき、numactlmbind(2) / set_mempolicy(2) で指定します。代表的な3つを比べます。

ポリシー割当先の決め方向く状況
local(既定)フォルトを起こしたコアのノードから割り当てスレッドとデータの局所性を最大化したい
bind指定したノード集合に限定して割り当て配置を厳密に固定したい・隔離したい
interleaveページ単位でノードを順番に分散全ノードへ均等アクセスし帯域を稼ぎたい

既定の local(first-touch とも呼ぶ) が重要な性質を持ちます。割当先は「ページを確保したスレッド」ではなく「そのページに最初に触れたスレッドが走っていたノード」で決まる、という点です。

malloc(巨大な配列)           # まだ物理ページは割り当てられない(仮想だけ予約)
for 各要素: 配列[i] = 0      # ここで初めてフォルト → 触れたコアのノードに配置

つまり「確保したスレッド」と「実際に使うスレッド」が別ノードだと、データは確保側ノードに固まり、使用側からは全部リモートアクセスになります。並列コードでよく使う定石が first-touch 初期化——本番で各データを読むのと同じスレッド分担で初期化フォルトを起こし、データを使用ノードへ正しく分散させる手法です。

単一スレッド初期化が招くノード偏在

大きな配列をメインスレッド1つで memset してから OpenMP 等で並列処理すると、全ページが初期化を行ったノードに集中配置されます。以降そのノードのメモリコントローラとインターコネクトが集中アクセスで飽和し、他ノードのコアは延々リモートアクセスを強いられます。確保ではなく first-touch のタイミングと担当スレッドこそが配置を決める、という非直感的な点が事故の温床です。

interleave は逆の発想です。1つの大きな領域を意図的に全ノードへ分散させ、どのコアから見ても「半分ローカル・半分リモート」に均します。最良ケースの局所性は捨てる代わりに、特定ノードへの集中による飽和を避け、アクセスが全域に散る用途(巨大な共有ハッシュ表など)で総帯域を引き上げます。局所性を取るか、片寄り回避を取るかの設計判断です。

アロケータとの関係

物理ページの実体を配るのはカーネルのメモリアロケータで、buddy system の free list はノードごと(ゾーンごと)に独立して管理されます。メモリポリシーは「どのノードのフリーリストから先に取るか」の優先順を与え、アロケータはそれに従って候補ノードを巡回します。

希望ノードに空きが無いときの挙動も設計判断に直結します。bind で厳格に縛れば、そのノードが逼迫すると回収(reclaim)やスワップが起きてでもノードを守ろうとし、最悪 OOM に至ります。一方 local(preferred 相当の緩い扱い)なら、希望ノードが満杯のとき近い別ノードへフォールバックします。局所性の厳格さと、メモリ逼迫時の頑健さはトレードオフの関係にあります。

zone_reclaim_modeの落とし穴

vm.zone_reclaim_mode を有効にすると、ローカルノードが詰まったとき他ノードへフォールバックする前にローカルの回収を試みます。局所性は守られますが、ページキャッシュが頻繁に追い出され、ファイル I/O 主体のワークロード(DB やファイルサーバー)ではかえって性能が落ちることがあります。多くのディストリビューションが既定で無効にしているのはこのためです。

自動NUMAバランシングとスケジューラ連携

手動配置は強力ですが、すべてのアプリを書き換えるのは非現実的です。そこで Linux は 自動NUMAバランシング(Automatic NUMA Balancing) を備えます。狙いは「タスクと、そのタスクが実際に触るページを、同じノードへ自律的に寄せる」ことです。

仕組みは2段構えです。まずカーネルが定期的に一部のページのページテーブルエントリを意図的にアクセス不可(PROT_NONE 相当)に印付けします。タスクがそのページに触れると軽量なフォルト(NUMA hinting fault)が発生し、カーネルは「どのタスクが・どのノードのページに触れたか」を統計として記録します。これは TLB やテーブルウォークの仕組み(MMU/TLB の内部)を流用した、アクセス傾向のサンプリングです。

1. 定期スキャン: 一部ページのPTEを「アクセス不可」に印付け
2. タスクが触る → NUMA hinting fault 発生
3. 統計更新: そのタスクは「どのノードのメモリ」を多く触るか集計
4. 判断:
   - ページをタスクのいるノードへ移す(ページマイグレーション)  または
   - タスクを、よく触るページのあるノードへ移す(タスク移動)

集まった統計をもとにカーネルは2方向の是正を選びます。ページをタスク側へ動かすか、タスクをページ側へ動かすか。後者は CPU スケジューラとの連携で、タスクが多くアクセスするノードを「希望ノード」としてスケジューラに伝え、可能ならそのノードのコアへ寄せます。負荷分散(ロードバランシング)が複数ノードへ均そうとする力と、局所性が1ノードへ寄せようとする力のせめぎ合いを、CFS/後継スケジューラが重み付けして調停します。

移動はタダではない:振動と二重コスト

ページマイグレーションは物理コピーと全CPUのTLB無効化(シュートダウン)を伴う実コストです。アクセスパターンが定まらないと、寄せた直後に逆へ動かす振動(ping-pong)が起き、本来の計算より移動で時間を食う事態になります。レイテンシ重視で配置を自分で固定したい場合は、numactl --cpunodebind --membind で CPU とメモリを同一ノードにピン留めし、自動バランシング(numa_balancing)を切るのが定石です。

設計判断のチェックポイント

NUMA で押さえるべきは「local=first-touch で配置が決まる」「interleave は局所性を捨てて片寄りを防ぐ」「bind は厳格だが逼迫時に脆い」「自動バランシングは観測ベースで寄せるが移動コストと振動のリスクがある」の4点です。確保時ではなく最初に触れたときに配置が決まる点が、頻出かつ誤りやすい急所です。

まとめ

  • NUMA はメモリコントローラを分散してUMAの帯域限界を超えるが、代償としてノード間アクセスは遅延・帯域とも非対称になる。
  • 既定の local(first-touch) では、確保時ではなく最初に触れたノードへページが配置される。並列コードは first-touch 初期化で使用ノードへ正しく分散させる。
  • interleave は局所性を捨てて全ノードへ均し集中飽和を防ぐ、bind は厳格だがメモリ逼迫時に脆い——目的に応じて選ぶ。
  • 自動NUMAバランシングはアクセスを観測してページとタスクを同ノードへ寄せるが、移動コストと振動があるため、レイテンシ重視ではピン留めして自動化を切る判断も有効。

OS Article

NUMAとメモリアクセス局所性を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

NUMA

比較で見る軸

難易度: advanced / カテゴリ: OS / タグ数: 5

導入後に効く点

メモリポリシー(local/bind/interleave)でページの配置先ノードを制御し、局所性を取るか帯域を均すかを使い分ける。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
OS
タグ数
5

判断チェックリスト

  • 自社の用途が「NUMA / メモリ」に近いか確認する。
  • 強みである「NUMAではCPUとメモリがノードに分かれ、自ノードより遠いノードへのアクセスは帯域が低く遅延が大きい非対称コストを持つ。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

NUMAメモリスケジューラ局所性LinuxNUMAメモリスケジューラ
参考: 公式情報