プロセスをCPUコアに固定する:tasksetとnumactlでLinuxのパフォーマンスを最大限に引き出す方法

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

なぜLinuxにプロセスの実行CPUを任せるべきではないのか?

デフォルトでは、Linuxのスケジューラ(scheduler)は非常にインテリジェントです。空いているCPUにタスクを自動的に割り振ります。しかし、この柔軟性が、低レイテンシ(low latency)を要求するシステムや高負荷な環境では、時として仇となることがあります。

以前、Fintech系の取引所のシステム管理をしていた際、奇妙なバグに悩まされたことがありました。CPU使用率は30%程度なのに、レイテンシが時々2msから100msまで跳ね上がるのです。perfを使って詳しく調査したところ、スケジューラが頻繁に実行コアを切り替えていること(コンテキストスイッチ)が判明しました。プロセスが新しいコアに移動するたびに、L1/L2キャッシュ内のデータが破棄されます。CPUはRAMからデータを再ロードしなければならず、その結果、パフォーマンスが急激に低下していたのです。

その解決策こそが、CPU Affinity(CPU親和性)です。tasksetnumactlを使用することで、プロセスを特定のコアに固定して実行させることができます。これにより、キャッシュの局所性(cache locality)を最大限に活用し、リソースの競合を排除することが可能になります。

ツールのインストール

UbuntuやCentOSなどのほとんどのディストリビューションでは、util-linuxパッケージにtasksetが含まれています。一方、numactlはより複雑なサーバーアーキテクチャ向けであるため、通常は手動でインストールする必要があります。

Ubuntu/Debianでのインストール:

sudo apt update && sudo apt install numactl -y

RHEL/CentOS/AlmaLinuxでのインストール:

sudo yum install numactl -y

tasksetを使用した基本的なCPU Affinityの管理

tasksetは、コアのIDまたはCPUマスクを介してCPU Affinityを設定するための最も軽量なツールです。

新しいプロセスを指定したコアで実行する

負荷の高いPythonスクリプトをコア0とコア1で実行したい場合は、-c(cpu-list)オプションを使用します。

taskset -c 0,1 python3 heavy_script.py

実行中のプロセスのコアを固定する

変更を適用するためにアプリケーションを再起動する必要はありません。PID(プロセスID)があれば、希望のコアに「ロック」できます。例えば、PID 1234をコア2だけで実行させるには:

taskset -p -c 2 1234

現在の状態を確認する

プロセスがどのコアでの実行を許可されているかを確認するには、次のコマンドを使用します。

taskset -cp 1234
# 実行結果: pid 1234 の現在のコア割り当てリスト: 0,1

numactlによるマルチソケットシステムの最適化

2つまたは4つの物理CPUを搭載したサーバーでは、NUMA(Non-Uniform Memory Access)という概念が極めて重要になります。各CPUは独自のRAM領域(ローカルメモリ)を管理しています。もしCPU 0がCPU 1のRAM領域からデータを取得しなければならない場合、ソケット間の帯域幅のボトルネックにより、速度が30〜50%ほど低下します。

numactlを使用すると、プロセスをCPUコアと対応するRAM領域の両方に固定し、最高の速度を実現できます。

NUMA構造の確認

設定を行う前に、サーバーにいくつのノードがあるか確認しましょう:

numactl --hardware

このコマンドにより、各ノードごとのコアリストと、ノード間の距離(distance)が表示されます。

NUMAを最適化してデータベースを実行する

大規模なサーバーでMySQLやMongoDBを実行する場合、レイテンシを避けるために同じノード上のリソースを使用するように強制します:

numactl --cpunodebind=0 --membind=0 /usr/bin/mongod --config /etc/mongod.conf

解説:

  • --cpunodebind=0: ノード0に属するコアのみで実行。
  • --membind=0: ノード0のRAMのみを使用。

ノード0のメモリ不足によるクラッシュが心配な場合は、--preferred=0を使用してください。これにより、ノード0を優先しますが、必要に応じて他のノードからメモリを借用することを許可します。

実際の運用におけるプロセスの監視方法

コマンドを打って終わりではありません。プロセスが実際に指定したコアに留まっているかを確認する必要があります。

htopを使用する

htopを開き、F2 (Setup)を押し、Columnsを選択します。PROCESSORという項目を探してF5を押し、監視テーブルに追加します。これで、各プロセスがどのコアIDを占有しているかが一目でわかります。

psコマンドを使用する

psコマンドのpsrカラムを使用して素早く確認できます:

ps -o pid,psr,comm -p 1234

リアルタイムで監視する

プロセスの移動を毎秒観察するには、watchを組み合わせます:

watch -n 1 "ps -o pid,psr,comm -p 1234"

導入における「痛い目を見て学んだ」経験則

何度もシステムの最適化を行った結果、3つの重要な注意点にたどり着きました:

  1. CPU 0を避ける: CPU 0は通常、ネットワークカードやディスクなどのハードウェア割り込み(interrupts)の処理に使用されます。重いアプリケーションをコア0に固定すると、システム全体のボトルネックになりやすいです。
  2. isolcpusとの組み合わせ: アプリケーションにコアを完全に独占させたい場合は、Grubの設定にisolcpusパラメータを追加してください。これにより、スケジューラがそのコアに他のプロセスを割り当てるのを防ぐことができます。
  3. ハイパースレッディング(Hyper-threading)を理解する: コア0とコア1が同じ物理コアを共有している場合があります。真の高速化を求めるなら、異なる物理コア(physical cores)に属するコアを選択してください。

CPU Affinityをマスターすることで、アプリケーションの安定性とプロフェッショナルな運用レベルが格段に向上します。Redis、Nginx、またはデータ処理ワーカーなどで、ぜひその速度の違いを体感してみてください。

Share: