TL

CPUアフィニティとロードバランシングの内部

なぜピン留めしたプロセスが速くなり、isolcpusで隔離した瞬間に安定するのかが腑に落ちます。スケジューラドメイン・定期/アイドル/wakeupの3種の均し方・キャッシュ温存を原理から押さえ、配置を設計判断で決められるようになります。

応用スケジューラCPUロードバランシングNUMAアフィニティLinux最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.ロードバランサはCPUをスケジューラドメインの階層(SMT→LLC→NUMA)として捉え、安いペナルティの近い階層から先にタスクを動かす。
  • 2.均しには定期(tick起点)・アイドル(idle直前のnewidle)・wakeup(起床時の配置先選択)の3系統があり、それぞれ目的とコストが違う。
  • 3.ピン留め(taskset/sched_setaffinity)はキャッシュ温存とNUMA局所性のための手段で、隔離(isolcpus/nohz_full)はバランサとtickそのものをCPUから外す上位の設計判断。

なぜ「全CPUを一つのキュー」にしないのか

マルチコア機でタスクを公平に走らせたいなら、全CPUで1本のグローバルランキューを共有すればよさそうに思えます。しかしそれは破綻します。ランキューは1つのロックで守られるため、コア数に比例してロック競合が悪化し、しかも別ソケットのコアへ無頓着にタスクを移すとL1/L2/LLCのキャッシュ温度を毎回捨ててしまうからです。

そこでLinuxはCPUごとに独立したランキューを持たせます(ランキュー構造)。ロック競合は局所化されますが、今度は別の問題が生まれます。あるCPUに4タスク、隣に0タスクという偏りが放置されると、せっかくのコアが遊びます。この偏りを定期的に均すのがロードバランシングで、本記事の主題です。鍵は「どこへ動かすか」をハードウェアの距離に基づいて決めることにあります。

スケジューラドメイン:CPUを距離の階層で捉える

カーネルはCPUトポロジを**スケジューラドメイン(sched domain)の入れ子として表現します。下位ほどCPU間が近く(移動が安く)、上位ほど遠い(移動が高い)という階層です。各ドメインはスケジューラグループ(sched group)**に分割され、バランシングはタスク単位ではなく「グループ間の負荷差」を見て判断します。

ドメイン階層共有するものタスク移動のペナルティ
SMT(同一物理コアのスレッド)実行ユニット・L1/L2極小。キャッシュをほぼ温存できる
MC(同一LLCを持つコア群)LLC(L3)小。L3が温かいまま移れる
NUMA(同一ノード/ノード間)メモリコントローラ大。リモートメモリ参照とLLCミスを伴う

バランサは下位ドメインから順に均しを試みます。SMT内で偏りが解消できればそこで止め、駄目なら一つ上のMC、それでも駄目ならNUMAへ——と範囲を広げます。これは「できるだけ安いペナルティで偏りを直す」貪欲な方針で、キャッシュコヒーレンシの観点でも、近いCPUへ移すほど無効化すべきキャッシュラインが少なくて済みます。負荷の指標はタスク数そのものではなく、各タスクの実行可能性を加重平均したPELT(Per-Entity Load Tracking)由来の負荷で、CFS/EEVDFの実行時間会計と同じ重み付けに基づきます。

ドメインはトポロジから自動構築される

sched domain の階層は管理者が手で組むものではなく、起動時にCPUID・ACPI(SRAT/SLIT)・デバイスツリーから読み取ったトポロジを基にカーネルが自動構築します。/sys/kernel/debug/sched/domains/ 配下を見ると、各CPUがどのドメイン・グループに属するかを確認できます。NUMA距離(SLIT)が階層の段数とコストに反映されるため、ハードウェアの物理構造がそのまま均しの方針を決めます。

3種類のロードバランシング

均しは単一の仕組みではなく、起動契機の違う3系統が役割分担します。混同すると「なぜこのタスクがこのCPUに居るのか」を読み違えます。

種類起動契機目的とコスト
定期(periodic)スケジューラtickの中で周期的に全体の偏りを定常的に均す。範囲が広く相対的に高コスト
newidle(アイドル直前)CPUがidleに入る直前遊びかけたCPUが他から仕事を引き込む。低レイテンシ優先で範囲は狭め
wakeup(起床時配置)タスクがblockから起きる瞬間起こされたタスクをどのCPUで走らせるかを選ぶ。移動ではなく配置の決定

定期バランシングはtickのたびに、ただし各ドメインに設定された間隔(balance_interval)を満たしたときだけ走ります。最も負荷の高いグループ(busiest group)を探し、そこから自CPUへタスクを引っ張るpull型が基本です。pull型を選ぶのは、引く側(暇なCPU)が自分のランキューロックを起点に動けて競合が小さいためです。

newidleバランシングは、あるCPUのランキューが空になりidleへ入る寸前に発火します。「アイドルするくらいなら近場から1つ引いてこよう」という発想で、レイテンシ重視ゆえに探索範囲を絞ります。これが効きすぎると逆効果になるため、sysctlsched_migration_cost_nsが「直近に走ったばかりのタスクはキャッシュが温かいので引かない」というガードとして働きます。

wakeupバランシングだけは性質が異なります。タスクが起床したとき、select_task_rqが走り先を選びます。ここでは「直前に走っていたCPU(キャッシュが温かい)」と「起こした側のCPU(データが近い可能性)」を天秤にかけ、wake_affineヒューリスティクスがLLCを共有する範囲で温かい方へ寄せます。プロデューサ/コンシューマ型ワークロードの性能はこの判断に強く依存します。

busyなCPUは自分からは動かない、が例外がある

定期/newidleは原則pull型ですが、暇なCPUが存在しないと偏りが直せない場面があります。これを補うのがactive balancingで、busiestなCPUの実行中タスクを別CPUへ強制的に押し出す(push)仕組みです。さらに、全CPUがbusyだと誰もバランサを起動しないため、NOHZ idle balancingでは代表CPUが、tickを止めて眠っている他CPUの分まで均しを肩代わりします。

キャッシュ温存とピン留め

タスクを動かすコストの正体はキャッシュの作り直しです。CPU-Aで走っていたタスクのワーキングセットはAのL1/L2に載っています。これをCPU-Bへ移すと、Bでは全てキャッシュミスから始まり、移動先がリモートNUMAノードならメモリ局所性まで崩れてリモート参照の遅延が上乗せされます。バランサがドメイン階層を下位優先で辿るのは、この作り直しコストを最小化するためでした。

それでもバランサの自動判断は確率的で、レイテンシが厳しいワークロードでは「たまに遠くへ飛ばされる」だけで尾レイテンシ(テールレイテンシ)が悪化します。そこで明示的に**CPUアフィニティ(ピン留め)**を設定し、タスクが走れるCPU集合を 0x1 のようなビットマスクで固定します。手段は tasksetsched_setaffinity(2) で、cgroup v2 なら cpuset.cpus でグループ単位に縛れます。

ピン留めは公平性とのトレードオフ

アフィニティを狭めると、そのタスクは指定CPUが混んでいても他の空きCPUへ逃げられません。バランサは許可マスク外へタスクを動かせない(can_migrate_taskがはじく)ため、ピン留めしたCPUに負荷が集中すると、隣が遊んでいても待ち行列が伸びます。キャッシュ温存と引き換えに全体スループットを犠牲にしうるので、「混ませないCPUを確保する」隔離とセットで考えるのが定石です。

隔離:isolcpusとnohz_full

ピン留めは「このタスクをここで走らせたい」という指定ですが、それだけでは指定CPUに他のタスクやバランサが入り込むのを防げません。レイテンシが最優先の用途(高頻度取引、リアルタイムDSP、DPDKのポーリングループ)では、CPUを丸ごと隔離して汎用スケジューラの管轄から外します。

カーネル起動パラメータ isolcpus=2,3 を渡すと、対象CPUはロードバランシングの対象から除外され、自動配置でタスクが流れ込まなくなります。そのCPUで走らせるには明示的なアフィニティ指定が必要になり、結果として「指定したスレッドだけが、邪魔されずに占有する」状態を作れます。さらに nohz_full=2,3 を併せると、そのCPUで実行可能タスクが1つだけのときスケジューラtickを止めることができ、周期割り込みによるジッタすら排除できます。

手段効果のレイヤ残る邪魔の例
sched_setaffinity(ピン留め)そのタスクの走行先を限定同CPUに他タスク・バランサが入り込む余地は残る
isolcpusCPUをバランサと自動配置から除外tick割り込み・一部カーネルスレッドは残る
nohz_full + 専有実行可能1つならtickまで停止IPI・RCUコールバックなど最小限

隔離は強力ですが、外したCPUの数だけ汎用プールが痩せます。OSの雑多な処理(カーネルスレッド、割り込みハンドラ、RCU猶予期間処理)は残りのCPUへ寄せる必要があり、irqaffinity や RCU の rcu_nocbs を併用してハウスキーピングCPUへ追い出す設計が伴います。隔離は単一の設定ではなく、tick・割り込み・RCU・バランサを一貫して外すシステム全体の設計判断だと捉えるのが正確です。

設計の順序:まず測り、最小限を隔離する

最初からCPUを大量隔離すると汎用処理が痩せて全体が不安定化します。実務の順序は、(1) まずアフィニティだけでキャッシュ温存を図り尾レイテンシを測る、(2) それでもジッタが残るなら最小本数を isolcpus で隔離、(3) 周期割り込み起因のジッタが支配的なら nohz_full まで踏み込む、です。隔離は効果が大きいぶん副作用も大きいため、常に「均しを外す代償」とセットで判断します。

まとめ

  • Linuxは全CPUで1本のキューを共有せず、CPUごとのランキューロードバランシングで偏りを均す。判断の土台はCPUをSMT→LLC→NUMAの距離で捉えるスケジューラドメイン階層で、下位(安い)から順に均す。
  • 均しには定期(tick起点でpull)・newidle(idle直前に近場から引く)・wakeup(起床タスクの配置先選択)の3系統があり、目的とコストが異なる。migration_costwake_affine がキャッシュ温度を守るガードとして働く。
  • タスク移動コストの正体はキャッシュの作り直しとNUMAリモート参照。ピン留めはこれを抑える手段だが、公平性と引き換えになる。
  • レイテンシ最優先ならisolcpus/nohz_fullでCPUをバランサ・tickごと隔離する。ただし隔離は割り込み・RCU・ハウスキーピングまで一貫して設計すべき上位判断で、「まず測り、最小限を外す」順序が安全。

OS Article

CPUアフィニティとロードバランシングの内部を実務で読む

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

解決すること

スケジューラ

比較で見る軸

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

導入後に効く点

均しには定期(tick起点)・アイドル(idle直前のnewidle)・wakeup(起床時の配置先選択)の3系統があり、それぞれ目的とコストが違う。

先に潰すリスク

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

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

判断チェックリスト

  • 自社の用途が「スケジューラ / CPU」に近いか確認する。
  • 強みである「ロードバランサはCPUをスケジューラドメインの階層(SMT→LLC→NUMA)として捉え、安いペナルティの近い階層から先にタスクを動かす。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

スケジューラCPUロードバランシングNUMAアフィニティスケジューラCPUロードバランシング
参考: 公式情報