← システム性能分析のしくみ コース
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 または SI/O完了、カーネルが起床させる
実行可能ランキューに載る
ここが待ち区間スケジューラに選ばれる
実行中再びCPU上で計算
- ・起床した瞬間に走れるとは限りません。CPUが他のタスクで埋まっていれば順番を待ちます
3段目の「実行可能」から4段目の「実行中」までの間隔が、このレッスンで扱うランキュー待ち時間です。アプリから見ると原因不明の遅延に見えます。
③待ち時間はナノ秒単位で観測できる
/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つのカウンタで見分ける
/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使用率だけを見ていると見落とすランキュー待ちの正体です。