Dev Study
システム性能分析のしくみ コース

5. ランキュー待ち — 実行可能なのに走れない時間

タスクが「実行可能になってから実際にCPUに載るまで」の待ち時間を図で追っていきましょう。

①タスクの状態は3つに分かれる

実行中・待機・実行可能の区別
実行中CPUを実際に使っている
スリープI/Oや応答を待っている
実行可能走れるがCPUが空いていない
/proc/PID/status の State 行で見える
R (running)実行中か実行可能
S (sleeping)割り込み可能な待ち
D (disk sleep)割り込み不可の待ち
  • ・proc_pid_status(5) の State は R / S / D / T / t / Z / X のいずれかです
  • ・R は「実行中」と「実行可能で待機中」を区別しません。両方まとめて R です

注目すべきは3つ目の「実行可能なのに走れていない」状態です。仕事の準備は整っているのにCPUの順番が回ってこない時間で、State だけを見ても実行中と区別できません。

②1周期の流れを追う

実行→スリープ→起床→ランキュー待ち→実行
実行中CPU上で計算している
read() を呼びディスク応答を待つ
スリープ自らCPUを手放す
State=D または S
I/O完了、カーネルが起床させる
実行可能ランキューに載る
ここが待ち区間
スケジューラに選ばれる
実行中再びCPU上で計算
  • ・起床した瞬間に走れるとは限りません。CPUが他のタスクで埋まっていれば順番を待ちます

3段目の「実行可能」から4段目の「実行中」までの間隔が、このレッスンで扱うランキュー待ち時間です。アプリから見ると原因不明の遅延に見えます。

③待ち時間はナノ秒単位で観測できる

/proc/PID/schedstat の3つの数値
/proc/PID/schedstat空白区切りで3つの値が並ぶ
左から順に
第1フィールドtime spent on the cpu
ナノ秒
第2フィールドtime spent waiting on a runqueue
ナノ秒
第3フィールドtimeslices run on this cpu
回数
  • ・docs.kernel.org の scheduler/sched-stats に3フィールドの定義があります
  • ・いずれも累積値です。2回読んで差分を取ることでその区間の待ち時間が分かります
  • ・第2フィールドの差分を第3フィールドの差分で割ると、タイムスライス1回あたりの平均待ち時間の目安になります

第2フィールドが積み上がる速度がランキュー待ちの実測値です。第1フィールド(実行時間)より第2フィールドの伸びが速ければ、走っている時間より待っている時間の方が長いことになります。

④CPU使用率100%でなくても待ちは起きる

平均使用率が低くても瞬間的に競合する
1秒平均の使用率 50%監視画面上は余裕に見える
実際の中身を細かく見ると
前半 0.5秒ほぼアイドル
後半 0.5秒4タスクが同時に起床
コアが2つしかない場合
2タスクが実行中CPUを確保できた
2タスクが実行可能ランキューで待機
schedstat 第2が増える
  • ・使用率は一定期間の平均です。期間内の偏りは平均に現れません
  • ・コア間の偏りも同じ現象を生みます。全体では空きがあっても特定コアのランキューだけが混むことがあります

平均使用率とランキュー待ちは別の指標です。使用率に余裕があるのに応答が遅い場合、この「バーストと偏り」を疑うのが定石です。

⑤なぜCPUを手放したのかを2種類に分ける

自発的コンテキストスイッチと非自発的コンテキストスイッチ
CPUを手放す別タスクへの切り替えが発生
手放した理由で2つに分かれる
自発的自分から待ちに入った
I/O待ち・ロック待ち・sleep
非自発的走れるのに取り上げられた
タイムスライス満了・プリエンプト
その後の状態が決定的に違う
スリープへ起こされるまで走る気がない
実行可能のまますぐランキューに戻される
  • ・sched(7) はリアルタイムポリシーについて「より高い静的優先度のスレッドが実行可能になると、現在実行中のスレッドはプリエンプトされ、その静的優先度の待ちリストに戻される」と説明しています
  • ・通常のタスクでも考え方は同じで、非自発的に取り上げられた直後はスリープではなく実行可能状態のまま並び直します

自発的な切り替えは待つのが仕事なので当然です。問題は非自発的な方で、走る意思も準備もあるのに取り上げられているため、そのぶんがランキュー待ちに変わります。

⑥/proc/PID/status の2つのカウンタで見分ける

voluntary と nonvoluntary の伸び方を比べる
/proc/PID/status末尾付近に2行ある
それぞれ累積回数
voluntary_ctxt_switches自発的に手放した回数
Linux 2.6.23 から
nonvoluntary_ctxt_switches取り上げられた回数
Linux 2.6.23 から
伸び方のパターンで切り分ける
voluntary が主に伸びるI/O待ちが中心
nonvoluntary が急増CPUの奪い合いを疑う
  • ・proc_pid_status(5) に voluntary_ctxt_switches と nonvoluntary_ctxt_switches が記載されています
  • ・どちらも累積値なので、1回読むだけでなく間隔をあけて2回読み差分を見ます

nonvoluntary の差分が大きいということは、そのプロセスが繰り返し途中で追い出されているということです。schedstat 第2フィールドの伸びと合わせて見ると裏付けが取れます。

⑦なぜ非自発が増えるとレイテンシが悪化するのか

取り上げられる回数と待ち時間の関係
1つの処理に必要なCPU時間合計 10ms
競合が無く一度も追い出されない場合
10ms を連続実行ランキュー待ちは0回
完了まで約10ms
競合があり途中で4回取り上げられた場合
実行の断片が5つに割れるnonvoluntary が4増える
断片の間に4回の待ち毎回ランキューに並び直す
完了まで10ms+待ち4回分
仮に待ちが1回あたり5msなら
完了まで約30msCPU時間は同じでも3倍
数値は一例
  • ・待ちタスクが増えるほど、並び直してから再び選ばれるまでの間隔が延びます
  • ・選ぶ規則自体はカーネル世代で変わります。Linux 6.6 以降の既定は EEVDF で、vruntime 最小ではなく仮想デッドラインが最も早いタスクを選びます

消費CPU時間が同じでも、細切れにされるほど待ち時間が積み重なって応答時間が伸びます。これがCPU使用率だけを見ていると見落とすランキュー待ちの正体です。

公式ドキュメントで詳しく ↗