私たちみんながCPU使用率として使っている指標は非常に誤解を招くもので、この状況は毎年悪化しています。CPU使用率とは何でしょうか?プロセッサーがどのくらい忙しいか?違います。CPU使用率が表しているのはそれではありません。私が話しているのは、あちこちで、あらゆる人たちに、あらゆる監視製品で、あるいはtop(1)でも使われている、"%CPU"という指標のことです。
あなたの考えているであろうCPU使用率90% :
実際 :
"stalled"(訳注 : 以下ストールと言う)とは、プロセッサーが命令の実行を進めていないことを意味していて、これは通常メモリーのI/Oを待っているために発生します。上に描いた(BusyとStalledの)比率は、私が本番環境でよく見るものです。ほとんどストールしていても、それを知らない可能性が高いのです。
これはどういうことでしょうか?どのくらいCPUがストールしているかを理解することで、コードを減らすかメモリーI/Oを減らすかの間でのパフォーマンスチューニングの手がかりをくれる可能性があります。CPUのパフォーマンスを見る人なら誰にとっても、CPUがオートスケールするクラウド上では特に、%CPUの中でストールしているコンポーネントを知る価値は大きいのです。
CPU使用率とはつまり何なのか
私たちがCPU使用率と呼んでいる指標は、実際は「アイドルでない時間」、つまりCPUがアイドルスレッドを実行していない時間です。オペレーティングシステムのカーネル(それがなんであろうと)は、通常コンテキストスイッチの間にこれをトラッキングします。アイドルでないスレッドが動き始め、それから100ミリ秒止まったら、その時間CPUはずっと使用されていたとカーネルは認識します。
この指標はタイムシェアリングシステムと同じぐらい古いものです。アポロの月着陸船誘導コンピューター(タイムシェアリングシステムの先駆者)では、アイドルスレッドのことを「ダミージョブ」と呼んでおり、エンジニアたちはそのサイクルと実際のタスクを実行するサイクルを、重要なコンピューターの使用率指標として監視していました(これについては以前にも書きました)。
では、これの何が問題なのでしょうか?
今日では、CPUはメモリーよりもずっと速くなってきており、メモリを待つこともいまだに「CPU使用率」と呼ぶことが多いのです。top(1)で%CPUが高い時には、ヒートシンクとファンの下にあるあのCPUパッケージ、つまりプロセッサーがボトルネックになっていることを考えるでしょう。しかし実はボトルネックはDRAMのバンクなのです。
この状況はどんどん悪化しています。長い間、プロセッサー製造業者はDRAMのアクセスレイテンシーの高速化よりも早くクロックスピードの高速化をしてきました(「CPU・DRAMギャップ」といいます)。2005年に3GHzのプロセッサーで、このギャップはほぼゼロになりましたが、それからと言うもの、メモリーのサブシステムにより大きな要求をする、複数コアやハイパースレッディング、さらにはマルチソケット構成といった仕組みを使ってプロセッサーはスケールしてきました。プロセッサー製造業者は、大きくかつスマートなCPUキャッシュや高速なメモリーバスやインターコネクトでこのメモリーのボトルネックを小さくしようと頑張ってきましたが、それでもまだストールはよく発生してしまいます。
CPUが本当は何をしているかを伝えるには
Linux perfなどを使って読み出せるハードウェアカウンターであるPerformance Monitoring Counters (PMC)を使います。例えば、システム全体を10秒間監視してみましょう。
# perf stat -a -- sleep 10
Performance counter stats for 'system wide':
641398.723351 task-clock (msec) # 64.116 CPUs utilized (100.00%)
379,651 context-switches # 0.592 K/sec (100.00%)
51,546 cpu-migrations # 0.080 K/sec (100.00%)
13,423,039 page-faults # 0.021 M/sec
1,433,972,173,374 cycles # 2.236 GHz (75.02%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
1,118,336,816,068 instructions # 0.78 insns per cycle (75.01%)
249,644,142,804 branches # 389.218 M/sec (75.01%)
7,791,449,769 branch-misses # 3.12% of all branches (75.01%)
10.003794539 seconds time elapsed
ここでの鍵となる指標は、各CPUクロックサイクルで完了した命令の数の平均を表す、サイクル毎の命令数です(insns per cycle: IPC)。(単純化すると)高ければ高いほど良いことになります。上の例での0.78という数字は、悪くはありません(78%ビジー?)。ただしそれはこのプロセッサーのトップスピードがIPC 4.0であることに気付くまでは、です。これは4-wideとも呼ばれ、命令のフェッチやデコードのパスを指しています。つまり、このCPUはクロックサイクル毎に4つの命令を完了することができると言うことです。従って、4-wideシステムでのIPC 0.78という数値は、CPUはトップスピードの19.5%で動いていることを表します。IntelのSkylakeプロセッサーは、5-wideです。
詳しく調査するために他にも使えるPMCがたくさんあり、異なるタイプから直接ストールしたサイクルを計測できます。
クラウドの場合
仮想環境を使っている場合、ハイパーバイザーがゲストに対してサポートしているかによって、PMCにアクセスできないこともあるかもしれません。私は最近、XenベースのAWS EC2クラウドのハードウェア専有ホストでPMCがどのように使えるようになったかを書いた「The PMCs of EC2: Measureing IPC」という記事を投稿したので、参考にしてください。
考え方と取るべき対策
IPCが1.0よりも小さい場合、メモリーがストールしている可能性が高く、メモリーのI/Oを減らしたりCPUキャッシングやメモリーの局所化を改善する(特にNUMAシステムでは有効)といった、ソフトウェアのチューニング戦略が取れます。また、大きなCPUキャッシュを持つCPUを使ったり、高速なメモリー、バス、インターコネクトを使うといったハードウェアチューニングも有効です。
IPCが1.0よりも大きい場合、命令がボトルネックである可能性が高いです。コードの実行を減らす方法を探しましょう。例えば不要な操作を減らす、命令をキャッシュするなどです。CPUフレームグラフは調査に非常に便利なツールです。ハードウェアチューニングとしては、高速なクロックレートやより多くのコアやハイパースレッドを試してみましょう。
上の例で私はIPC 1.0で分けています。どこからこの値を持ってきたのでしょうか?これは、私が今まで色々なPMCに取り組んできたものを元にして出したものです。あなたのシステムとランタイム向けの値を出すことができます。ひとつはCPUバウンド、もうひとつはメモリーバウンドの2つのダミーの負荷をかけてみて、IPCを計算し、その中間値を計算すれば良いのです。
パフォーマンス監視製品が本来伝えるべき情報
あらゆるパフォーマンスツールは、%CPUと一緒にIPCを表示するべきです。あるいは%CPUを、命令の完了サイクルとストールのサイクル、例えば%INSと%STLの対比にブレークダウンするべきでしょう。
top(1)に対しては、Linuxではtiptop(1)があり、プロセス毎のIPCを表示してくれます。
tiptop - [root]
Tasks: 96 total, 3 displayed screen 0: default
PID [ %CPU] %SYS P Mcycle Minstr IPC %MISS %BMIS %BUS COMMAND
3897 35.3 28.5 4 274.06 178.23 0.65 0.06 0.00 0.0 java
1319+ 5.5 2.6 6 87.32 125.55 1.44 0.34 0.26 0.0 nm-applet
900 0.9 0.0 6 25.91 55.55 2.14 0.12 0.21 0.0 dbus-daemo
CPU使用率が誤解を招く他の理由
CPU使用率を誤解を招くものにしているのは、メモリーのストールのサイクルだけではありません。他にはこんなものがあります。
- プロセッサーをストールさせる温度の変化
- クロックレートを変化させるターボブースト機能
- SpeedStepによってクロックレートを変化させるカーネル
- 平均の問題点 : 1分間の80%使用は、使用率100%のバーストを隠している可能性がある
- スピンロック : CPUは使用されていてIPCも高いが、論理的にはアプリケーションの処理が進んでいない
追記 : CPU使用率は実際よくないのか?
この記事に対して、この記事の下やその他の場所(1, 2)でたくさんのコメントがありました。このトピックに興味を持ってくれて、皆さんありがとうございます。これに対する回答を簡単に言えば、iowaitについてはここでは全く取り上げておらず(それはディスクI/Oの話です)、メモリーバウンドであることが分かっていれば、やれることがあるということです(上に記述済み)。
しかし、そもそもCPU使用率は実際よくないのか、あるいはとても誤解を誘うものなだけなのでしょうか?私が思うに、多くの人はCPU使用率が高いことをプロセッシング装置がボトルネックである証拠であり、それは悪いことだと考えています。その時点ではあなたはまだ分かっていないだけで、それは外的な何かであることがよくあります。ではCPU使用率は技術的には正しいのでしょうか?CPUストールのサイクルが他のことに使われないなら、それは「待つために使用中」(矛盾して聞こえますね)なのでしょうか?場合によっては答えはYesで、OSレベルでの指標としてはCPU使用率は技術的に正しいと言えますが、しかしとても誤解を誘うものでもあります。ハイパースレッドではこれらのストールサイクルは別のスレッドで使用できるので、CPU使用率は実は使用可能なサイクルを含んでしまう可能性があるのです。これはよくありません。この記事で私は、解釈の問題とそれに対する解決策に焦点を当てたかったのですが、一方でCPU使用率には技術的な問題も存在しています。
Adrian Cockcroft氏が以前指摘したように、指標としての使用率はもう既に使い物にならないという人もいるかもしれません。
結論
CPU使用率は、非常に誤解を招く指標になりつつあります。最近の負荷の多くを占める、メインメモリを待っているサイクルを含んだ値になっているためです。サイクル毎の命令数(IPC)のような他の指標を使うことで、%CPUが実際何を意味しているかを知ることができます。IPCが1.0より小さければメモリーバウンド、IPCが1.0より大きければ命令バウンドである可能性が高いことを示します。IPCについては、計測しておくべきPerformance Monitoring Counter(PMC)を含め、私の前の記事で取り上げました。
%CPUを表示するあらゆるパフォーマンス監視製品は、どういう意味なのかを明示し、エンドユーザに誤解を与えないためにPMCの指標も表示するべきです。例えば、%CPUと一緒にIPCを表示したり、命令が完了したサイクルとストールしたサイクルの比率を表示したりといったことです。これらの指標で武装しておけば、開発者もオペレーターもアプリケーションやシステムを上手にチューニングする方法を選ぶことができるでしょう。