「詳解システムパフォーマンス」の読書メモシリーズ・第4弾。
- 詳解システムパフォーマンスを読んでいる話・2章/メソドロジ 読書メモ - えいのうにっき
- 読書メモ・詳解システムパフォーマンス 第3章/オペレーティングシステム - えいのうにっき
- 読書メモ・詳解システムパフォーマンス 第4章/可観測性ツール - えいのうにっき
- 作者: Brendan Gregg,西脇靖紘,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2017/02/22
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
この章の輪読会は今週の木曜日に予定されているのだけど、業務の関係で参加できないことが決っているので、開催自体はまだだけど自分の読書メモは先行して公開しておく。
ちなみに「業務」は、自社プロダクトの下記イベントへのブース出展、なので、よろしければこちらの会場にもぜひお越し下さい(宣伝)。
感想
前章の「可観測性ツール」とは違い、「アプリケーション」ということで、「ずっとアプリケーションエンジニアをやってきたし、少なからずパフォーマンス問題を対象に取り組んできたこともあるから、この章はかなりの実感を持って読み進められるはず!」と、若干意気込みながら読んだ。
が、実際に読んでみると、「アプリケーションパフォーマンス分析」と一口に言っても、
- アプリケーションが処理する要求・どのようなオペレーションを行なうのか
- アプリケーションのCPUモード(ユーザーモード/カーネルモード)はなにか
- I/O サイズはどうか
- キャッシュの有無と実効性は適切か
- 並列実行を意図して並行実行になっていないか
- 並列実行の場合、ロックの種類や発生状況はどうか
- アプリケーションが実装されている言語の種類とそれに合ったパフォーマンス分析手法はなにか
- アプリケーションパフォーマンスの分析はどのような手法をどのような手順で実施していくべきか
...と、非常に多岐に渡っており、今まで自分が「アプリケーションパフォーマンスの改善をする/してきた」としてやってきたことや考えていたことがいかにそのごく一部(せいぜい、静的パフォーマンスチューニングの一部とメソドロジのいくつかを "つまみ食い" していた程度。。)であったか、ということを思い知らされた章だった。言い換えると、今までやってきたことは当てずっぽうだったり場当たり的なものであった(何かの知識やガイドに基づくものではなかった)、と言われても仕方がない、とも言える。
そして、この章を読んだからといって即これらを自分のものとすることは難しいだろうとも思う。次にアプリケーションパフォーマンスの問題に直面し、またその解決に当たらなければならなくなったときには、まずこの本のこの章のことを思い出し、再度見返したいと強く思った。
読書メモ
5.1 アプリケーションの基礎
アプリケーションにおける「システムパフォーマンス」を考える際におさえておくべき基礎的な事柄。
- 機能
- データベースサーバ / ウェブサーバ / ロードバランサ / ファイルサーバ / オブジェクトストア。どれも「アプリケーション」
- オペレーション
- アプリケーションが処理する要求・どのようなオペレーションを行なうのか、について押さえておく。
- データベースであればクエリを処理する
- Webサーバであれば HTTP リクエストを処理する
- オペレーションは「速さ」「ペース」として計測可能
- CPUモード
- 構成、設定
- アプリケーションはどのように構成、設定されているか。
- バッファサイズ / キャッシュサイズ / 並列処理(プロセス or スレッド) / その他パフォーマンスに関連するチューニング可能なパラメータが変更されているかどうか。
- 指標
- アプリケーションの指標が提供されているか。オペレーションのペースなど。
- ログ
- バージョン
- 最近のバージョンのリリースノートでパフォーマンスに関わる情報が掲載されているか。
- バグ
- 現在のバージョンのアプリケーションが、パフォーマンスに関わるバグを抱えていないかどうか。
- コミュニティ
- パフォーマンスについて何かわかったことをシェアするコミュニティがそのアプリケーションに存在しているかどうか。
- 書籍
- アプリケーションやそのパフォーマンスについての本の有無。
- エキスパート
- そのアプリケーションのパフォーマンスについてのエキスパートは誰か。彼が作った参考資料はあるか。
5.1.1 パフォーマンスの目標
パフォーマンス分析の際には、明確な目標を設定することが大事。それにより分析の方向性が定まり、どのようなアクティビティを実行するかを選択する上で役に立つ。
パフォーマンス目標を考える際には、「アプリケーションが実行するオペレーションは何か」「パフォーマンス目標はなにか」を考えるところから始める。「レイテンシ」「スループット」「リソース使用率」などが目標となりうる。さらにこれらを、ビジネスやQoS要件から派生した指標を使って、定量化する。
スループットを目標にする場合は、「すべての処理がパフォーマンスやコストの点で等しいわけでない」点に注意する。また「オペレーションのペース」を目標とする場合には、そのオペレーションタイプも合わせて設定することも大切。
5.1.2 よく実行されるコードの最適化
- アプリケーションパフォーマンスを効率よく向上させるには、「本番ワークロードでもっともよく通るコードパス」を見つけ出し、その部分を改善するところから始めるのが良い。
5.1.3 可観測性
- オペレーティングシステムでもアプリケーションでも、もっとも大きくパフォーマンスを引き上げられるのは、不要な仕事を取り除いたとき。
- 使用するアプリケーションを選択できる際には、可観測性ツールがどれくらい取り揃えられているか・豊富な方を選んだ方が長期的には良いことが多い
- 可観測性ツールを使うことで、不要な仕事を取り除くことができる場合がある。
5.1.4 ビッグオー記法
O(n)
みたいなやつ
5.2 アプリケーションのパフォーマンス向上のためのテクニック
5.2.1 I/O サイズの選択
- I/O の実行に伴うコストには、以下のようなものが含まれている。
- これらは I/O サイズが小さくても同じようにかかるため、これの効率を上げるには、1回の I/O で転送するデータを多くする、というアプローチがある
- ただし、対象のアプリケーションが大きな I/O サイズを必要としない場合には、無駄なデータ転送が増え、かえって遅くなってしまう場合もある
- I/O サイズとしてアプリケーションが要求しているサイズにもっとも近い小さな値を選ぶことによって I/O パフォーマンスは最適化することができる。
5.2.2 キャッシング
- キャッシュされているが古くなったデータがルックアップによって返されないように、データの完全性をどのようにして管理するか(キャッシュコヒーレンシ)が重要。
5.2.3 バッファリング
- 書き込みパフォーマンスを向上させるには、次のレイヤにデータを送る前に、書き込み予定データをバッファ内で結合すると、I/Oサイズが大きくなり、オペレーションの効率が上がる。
- ただし、バッファに対する最初の書き込み内容は、その後の書き込みが届くのを待たなければディスクにも書き出されないため、バッファリングによって書き込みレイテンシが高くなる場合もある
- リングバッファ(循環バッファ)
- コンポーネント間での継続的なデータ転送のために使える固定バッファの一種
- 転送は非同期で実施
- データが追加・削除されると、そのコンポーネントの分だけ前後に移動する先頭・末尾ポインタを使って実装することができる
- リングバッファとは: https://wa3.i-3-i.info/word14292.html
- 例えが斬新だけど、 まぁわかりやすかったかな。。
5.2.4 ポーリング
- ループ内でイベントのステータスをチェックしてイベントの発生を確認する手法。
- ポーリングには、パフォーマンス上の問題がある。
- 反復的なチェックにより、CPUのオーバーヘッドが高くなってしまう
- イベントの発生から次のチェックまでの間がそのままレイテンシとして計上されてしまう
- poll()システムコール:ファイルディスクリプタのステータスをチェックするためのシステムコール。イベントベースのポーリングなので、パフォーマンスコストはかからないが、ファイルディスクリプタの配列を扱う仕組みのため、イベント発生時には配列をスキャンするオーバーヘッドが発生してしまう(O(n))。
- epoll() を使うとスキャンを避けられる(O(1)になる)。
5.2.5 並行/並列処理
- 並行実行:1つのCPUの処理時間を小分けし、プログラムの実行を高速に切り替えることで同時に実行しているように見せる仕組み。1個のCPUしか使っていないのでスケーラビリティに問題がある。
- 並列実行:同時に複数のCPU上でプログラムの実行を行なう仕組み。アプリケーションがマルチプロセスかマルチプロセスでなければ実現されない。
- 同期プリミティブ
- メモリアクセスを規制するためのもの。
- ミューテックスロック:ロックを持つスレッドだけがCPUを使える
- スピンロック:スピンロックを持っているスレッドが処理を実行できる/その他のスレッドはロックが開放されていないかどうかをチェックしながらCPU上でタイトループに入ってロックスピンを獲得しようとする
- ロックを獲得しようとし続けている間、有益な仕事を何もせずに動作し続けるため、ブロックされたスレッドもCPUから排除されず、そのためロックを入手できたら数サイクルで実行を再開できるのでレイテンシの低いアクセスを実現できる。が、スピン(ループ)して待っている間にもCPUリソースを無駄に使ってしまう
- RWロック:複数の読み込みか、ひとつの書き込みのみの実行を認めることにより完全性を保障する。
- アダプティブミューテックスロック:スピンロックとミューテックスロックのハイブリッド。ロックを持っているスレッドが他のCPUで実行されていればスピンし、そうでなければブロックする。Linux では「アダプティブスピニングミューテックス」と呼ばれている
- ハッシュテーブル
- 大量のデータ構造体のために必要なロックの数について考える。
- 上記両者の中間で「ロックのハッシュテーブルを作る」方式がある。
- メモリ内にロックを隣り合わせに並べた配列を置くとパフォーマンス問題が起きる危険性がある
- 複数のロックが同じキャッシュラインに並んだとき、ふたつのCPUが同じキャッシュラインに含まれる別々のロックを更新しようとすると、ほかのキャッシュに含まれるキャッシュラインを無効化することになり(?)、キャッシュコヒーレンシのオーバーヘッドがかかるため。
それらデータが各コアのL2,L1にキャッシュされた際、コヒーレンシを維持するための通信コストが頻繁に発生して、劇的に遅くなったりすることがある
http://d.hatena.ne.jp/kaminarioyaji/20090106/1231207192- このような状況を偽共有という。
- ハッシュロックの間に未使用バイトをパディングし、個々のキャッシュラインに含まれるロックがひとつだけになるようにして解決する
5.2.6 ノンブロッキングI/O
- 現在のスレッドをブロックせずにI/Oを非同期に発行する仕組み
5.2.7 プロセッサのバインド
- NUMA : 各CPUにローカルメモリが予め割り当てられているような環境のこと。自身のローカルメモリーへは、他のCPUのアクセス状況に関わらず同時に並行してアクセスできるため、CPU数を増やしても、メモリーアクセスの遅延は増大しないという特徴がある
- ただし、他のCPUが抱えるメモリにデータが存在する場合は、遅延が大きいCPU間のバスを通じてアクセスする必要がある
- NUMA環境では、プロセスやスレッドを同じCPUで実行し続け、前回のI/O実行後に実行に使ったのと同じCPUで実行するとメリットがあり、オペレーティングシステムもそれを前提として、CPUにバインドされるよう設計されている(CPUアフィニティ)。
- これのリスクとしては、ほかのCPUはアイドル状態なのに、バインドされたCPUが他のアプリケーションによりビジー状態となりレイテンシが高くなる、といった現象が起きる場合がある。
5.3 プログラミング言語
5.3.1 コンパイル言語
- コンパイル後のマシン語コードがオリジナルのプログラムに正確に対応づけられているので、そのパフォーマンス分析は通常であれば容易。
- コンパイル時に、実行可能コードのアドレスからプログラムの関数、オブジェクト名を導き出せるシンボルテーブルを生成することができ、プロファイリングやトレーシング時にこれを用いることでプログラム内での名前が直接出力されるようにできる
- コンパイラは、最適化によってパフォーマンスを改善することができる。CPU命令の選択や配置を適切なものにするものが「最適化器」である。
- 最適化のレベルや選択するオプションなどによって、プログラム(分析対象)のふるまいやデバッグの容易性が変わってくるため、慎重に選択する必要がある。
5.3.2 インタープリタ言語
- 可観測性ツールが提供されていなければ困難になりうる
- インタープリタそのものの分析になってしまうケースも。
- パーサーの動的トレーシングなど、インタープリタによってはプログラムのコンテキストを間接的ながら簡単に取り出せる場合がある
5.3.3 仮想マシン
- アプリケーションプログラムは仮想マシンのマシン語命令セット(バイトコード)にコンパイルされ、仮想マシンによって実行される。
- パフォーマンス分析は、仮想マシンとともに提供されるツールセットとサードパーティツールを中心としたものになる
- プログラムがCPU上で実行されるまでに、コンパイル・解釈といったステージを複数くぐり抜けるため、一般に可観測性という点では最も難しいタイプの言語。
5.3.4 ガベージコレクション
- メモリ管理が自動化・アロケートしたメモリを明示的に開放しなくてよい言語では、メモリの開放は非同期に実行されるガベージコレクションプロセスに任せられる。
- 以下のようなデメリットもある。
- メモリ消費量の増加:オブジェクトが自動的に開放可能だと判断されない場合には、メモリの消費が増える可能性がある
- CPUコスト:ガベージコレクションは断続的に実行され、メモリ内オブジェクトのサーチ・スキャンを行なうため、CPUリソースを消費する。アプリケーションのメモリ消費量が増えるとガベージコレクションが消費するCPU時間も増える
- レイテンシ外れ値:ガベージコレクションの実行中にアプリケーションの実行が一時停止すると、アプリケーションが反応するまでのレイテンシが非常に高くなることがある。これはガベージコレクションのタイプによっても変わる。
5.4 メソドロジと分析
5.4.1 スレッドの状態の分析
- 分析対象のスレッドは、「CPU上にある状態」なのか「CPU外にある状態」なのか。
- 「CPU外にある状態」はさらに細かい状態に細分化できる
- 実行可能:CPU時間を得る順序を待っている状態。アプリケーションがもっと多くのCPUリソースを必要としているということ。
- 無名ページング(スワッピング):実行可能だが、無名ページのページインを待ってブロックしている状態。アプリケーションが使えるメインメモリが不足している可能性がある
- スリープ:ネットワーク・ブロックデバイス・データ/コードのページインなど、I/Oを待っている状態。ブロックさせているリソースを解析する。
- ロック:同期ロックの獲得を待っている状態。どのロックで・それを保持しているスレッドは何か・長時間ロックを保持しているのはなぜか、を明らかにする。
- アイドル:ワークロードを待っている状態。この時間が長ければ、アプリケーションの要求レイテンシは低く、アプリケーションはもっと多くの負荷を処理できるという意味になる
- 「要求を待つ」というアプリケーションの性質上、スリープ・ロック状態の時間も実際にはアイドル時間であるという場合がよくあるので、これらを分析するときには少し掘り下げてみる必要がある。
- 実行のために費やされている時間は top(1) で簡単にわかる。
- 実行可能状態で費やされている時間は、カーネルの schedstat(/proc/*/schedstat)で追跡できる
- 無名ページング(スワッピング)を待っている時間は、カーネルの遅延アカウンティングによって計測可能。
- スワッピングとメモリ回収によるブロック時間とで状態を分けている
- スリープ状態でブロックされている時間は、たとえば
pidstar -d
を使うことでおおよその値を推計することができる- 長期(数秒)に渡ってアプリケーションがスリープ状態になっている場合は、pstack(1)(スレッドとユーザースタックトレースをひとつのスナップショットにまとめる)を使えば理由がわかる場合がある
- ロック時間はトレーシングツールで調査可能。
5.4.2 CPUのプロファイリング
- 詳細は6章。
- プロファイリングの目的は「アプリケーションがCPUリソースを消費しているのはなぜかを明らかにすること」。
- そのためには、CPU上のスレッドのユーザーレベルスタックトレースをサンプリングし、結果を結合するのが効果的。
- 仮想マシンCPUの使用状況を調べるのは難しいが、たとえば DTrace には ustack ヘルパーがあり、これを使うことで VM を覗き込み、スタックをオリジナルのプログラムに翻訳することができる。
5.4.3 システムコールの分析
- 「ユーザーモードとして実行中か」「システムコール中(カーネルモードとして実行中、もしくは待っている)か」という観点で解析したほうが役に立つ場合もある
- システムコールは複数の方法で解析できる。その目的は、「syscall 状態の時間はどこで使われているのか」「システムコールのタイプとそれが呼び出された理由はなにか」を明らかにすること。
- ブレークポイントトレーシング:システムコールのエントリとリターンにブレークポイントを設定する伝統的なトレーシング方法。システムコールを頻繁に呼び出すアプリケーションでは、パフォーマンスが桁違いに悪くなる場合がある。Linux では strace(1) など。
- バッファードトレーシング:ターゲットプログラムの実行を継続しつつ、インストルメンテーションデータはカーネル内にバッファリングすることができる方法。DTrace もこれ。
5.4.4 I/Oのプロファイリング
5.4.5 ワークロードの特性の把握
- アプリケーションは、システムリソースに負荷をかける。
- それにはワークロードによって特性がある
5.4.6 USEメソッド
- すべてのハードウェアリソースの使用率・飽和・エラーをチェックするもの。
- アプリケーションによっては、ソフトウェアリソースにも USE メソッドを応用できる
- 問題としては、これらの指標を計測するための方法を見つけること、になる。
5.4.7 ドリルダウン分析
- まずアプリケーションが提供するオペレーションを解析し、アプリケーションがそれらをどのように実行しているのかを理解するためにアプリケーションの内部構造をドリルダウンするという形になる
- I/O については、システムライブラリ・システムコール・カーネルにも入り込んでいく場合がある
5.4.8 ロック分析
- ロックは「競合のチェック」「長すぎるロック保持のチェック」により分析することができる。
- 「競合のチェック」により、「今問題があるかどうか」を判定できる
- 「「長すぎるロック保持」はかならずしも問題とは言い切れないが、将来的に並列負荷が増えると問題になる場合がある
- ロックの名前とロックを使おうとするに至ったコードパスをはっきりさせる。
- スピンロックの場合、競合はCPUの使用状況に現れるので、CPUプロファイリングによっても発見できる。
5.4.9 静的パフォーマンスチューニング
アプリケーションパフォーマンスの場合、以下のような静的構成の側面をチェックする。
- アプリケーションのバージョン。どのバージョンなのか・新しいバージョンはあるか・リリースノートでパフォーマンスの向上についての言及はあるか。
- 既知のパフォーマンス問題はあるか・それは何か。バグデータベースはあるか。
- アプリケーションはどのように構成されているか。
- 構成やチューニングがデフォルトとは異なる場合、その理由はなにか。
- オブジェクトキャッシュを使っているか。サイズはどれくらいか。
- 並列実行可能か。どのように構成されているか(スレッドプールのサイズなど)。
- たとえばデバッグモードが有効になっていないかどうかなど、特別なモードで実行されていないかどうか。
- どのシステムライブラリを使っているか。そのバージョン。
- アプリケーションが使っているメモリアロケータはなにか。
- ヒープのために大きなページを使うように構成されているか。
- コンパイルされているかどうか。されている場合、コンパイラオプションと最適化はどうなっているか。また、64ビットかどうか。
- アプリケーションがエラーを起こし、縮退モードで実行されてしまっていないか。
- CPU、メモリ、ファイルシステム、ディスク、ネットワークの利用に制限やリソースコントロールが加わっていないか。
練習問題
各種用語について
- キャッシュとはなにか
- 実行頻度の高いオペレーションの結果を再利用できるよう、アクセスパフォーマンスの高いローカルキャッシュに格納することで、コストの高いオペレーションの実行そのものを回避するための仕組み。
- キャッシュされているが古くなったデータがルックアップによって返されないように、データの完全性をどのようにして管理するか(キャッシュコヒーレンシ)が重要。
- リングバッファとはなにか
- スピンロックとはなにか
- スピンロックを持っているスレッドが処理を実行できる/その他のスレッドはロックが開放されていないかどうかをチェックしながらCPU上でタイトループに入ってロックスピンを獲得しようとするもの。
- ロックを獲得しようとし続けている間、有益な仕事を何もせずに動作し続けるため、ブロックされたスレッドもCPUから排除されず、そのためロックを入手できたら数サイクルで実行を再開できるのでレイテンシの低いアクセスを実現できる。が、スピン(ループ)して待っている間にもCPUリソースを無駄に使ってしまう
- アダプティブミューテックスロックとは何か
- 並行処理と並列処理の違いはなにか
- 並行処理:1つのCPUの処理時間を小分けし、プログラムの実行を高速に切り替えることで同時に実行しているように見せる仕組み。1個のCPUしか使っていないのでスケーラビリティに問題がある。
- 並列処理:同時に複数のCPU上でプログラムの実行を行なう仕組み。アプリケーションがマルチプロセスかマルチプロセスでなければ実現されない。
- CPUアフィニティとはなにか
- あるプロセスがどの CPU プロセッサで実行されるか、ということ。
- Linux 上で、実行中のプロセス(コマンド)が、複数あるうちのどの CPU プロセッサと親和性があるのかを確認したり、親和性をとる CPU プロセッサを指定する方法についての記録のこと。
コンセプトについて
- 大きな I/O サイズを使うことについての一般的な長所、短所はなにか。
- I/O実行に伴うオーバーヘッドを節約することができる
- 対象のアプリケーションが大きな I/O サイズを必要としない場合には、無駄なデータ転送が増え、かえって遅くなってしまう場合もある
- I/O サイズとしてアプリケーションが要求しているサイズにもっとも近い小さな値を選ぶことによって I/O パフォーマンスは最適化することができる
- ロックのハッシュテーブルの用途はなにか。
- 大量のデータ構造体のためのロックを適切なサイズに抑えるために使用する。
- 並列処理を最大限に実現するには、ハッシュテーブルのバケット数はCPUの個数以上である必要がある。
- コンパイル言語・インタープリタ言語・仮想マシンを使う言語の実行時の一般的なパフォーマンスについて。
- ガベージコレクションの役割とはなにか。
- メモリ管理の自動化・明示的なメモリ解放処理の不要化により、プログラムコードをより書きやすくしてくれている
- ガベージコレクションがパフォーマンスにどのような影響を与えるか。
- オブジェクトが自動的に開放可能だと判断されない場合には、メモリの消費が増える可能性がある
- ガベージコレクションは断続的に実行され、メモリ内オブジェクトのサーチ・スキャンを行なうため、CPUリソースを消費する。
- アプリケーションのメモリ消費量が増えるとガベージコレクションが消費するCPU時間も増える
- ガベージコレクションの実行中にアプリケーションの実行が一時停止すると、アプリケーションが反応するまでのレイテンシが非常に高くなることがある。
第5章/アプリケーション についての読書メモは以上。