最近、パフォーマンス測定について学びがあったので基本的な内容が多いですが、自戒もこめてまとめておきますm( )m
大事なこと
- 思い込みを捨てる
- あたりをつけてそこから測定するのは大事だが、迷ったときにはここでは起きないはずといった思い込みは捨てて、ちゃんと網羅的に測定する。
- ボトルネック、発生箇所を場所を測定し特定する
- 条件を合わせる
- ローカルで同様の処理を実行して試す等を行う際は、実際に発生した状況との乖離をなるべく少なくする。
- 複数の対策を比較するときは検証方法や前提を揃えて測定する。
思い込みを捨てる
例えばどこかしらの処理でパフォーマンスの起きてる場合には、根拠を持ってあたりを付けて着手する(N+1が発生してそうな処理なので、そこがパフォーマンス劣化の原因になっていそう等)のは時間短縮的にも望ましいと思いますが、道に迷ったときには、全体像を思い浮かべて網羅的・定量的に測定してボトルネックを探すことが大事。
例) リクエストからレスポンスが返却されるまで(サーバー単位)
例) URL解決から結果が生成されるまで(Rails)
ボトルネック、発生箇所を場所を測定し特定する
コードを見てもパフォーマンスが遅くなっていそうな場所が見当たらない等の場合はシステムのパフォーマンスを測定して、どのような場所に負荷がかかっているのかを測定すると、どのような処理が影響してそうか判断するヒントになります。(コードではなく他のプロセスがサーバー上の負荷を上げてただけ等の事象にも気づけそうです。)
迷ったときはシステムで大まかに測定し、その結果をもとにコードを見ていくと答えが見つかるかもしれません。
システムのパフォーマンスを測定する
システムのパフォーマンスを測るために使えそうなcommand等のtipsを記載しておきます。
起動中のプロセスを確認する
ps aux
コマンドを使うと起動中のすべてのプロセスのCPU使用率やメモリの使用率を確認することが出来ます。これを使うと他のサービスによる影響等が確認できそうです。
$ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.8 2126072 36208 pts/0 Ssl+ Aug10 0:01 /usr/local/lib/erlang/erts-10.2.2/bin/beam.smp -- -root /usr/local/lib/erlang -progname erl -- -home /root -- -pa /usr/local/lib/elixir/bin root 29 0.0 0.0 4184 680 ? Ss Aug10 0:00 erl_child_setup 1048576 root 48 0.0 0.0 19948 3540 pts/1 Ss+ Aug10 0:00 bash root 64 0.0 0.0 19948 3704 pts/2 Ss 03:14 0:00 bash root 84 0.0 0.0 38384 3112 pts/2 R+ 03:16 0:00 ps aux
現在のメモリの空き容量を確認する
free -t
コマンドを使うと現在のメモリの空き容量をスワップ領域を含めて確認することが出来ます。これを見るとメモリの逼迫によるスワップ領域を使用した事によりパフォーマンスが劣化した可能性があるかどうか確認できそうです。
$ free -t total used free shared buff/cache available Mem: 4042484 323724 3371672 804 347088 3504124 Swap: 1048572 0 1048572 Total: 5091056 323724 4420244
システム全体の負荷状況を確認する
top
コマンドを使うとシステム全体の負荷状況をリアルタイムに確認することが出来ます。
-o %MEM
を指定するとメモリの使用量の降順に並び替えることも出来ます。topコマンドを確認しながら該当処理を実行すると、どのような影響が出ているかを確認できそうです。
$ top -o %MEM Tasks: 4 total, 1 running, 3 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.2 sy, 0.0 ni, 99.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 4042484 total, 3371332 free, 323748 used, 347404 buff/cache KiB Swap: 1048572 total, 1048572 free, 0 used. 3504084 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 2126072 36208 5316 S 0.0 0.9 0:00.94 beam.smp 48 root 20 0 19948 3540 2988 S 0.0 0.1 0:00.04 bash 63 root 20 0 42776 3432 2972 R 0.3 0.1 0:00.01 top 29 root 20 0 4184 680 612 S 0.0 0.0 0:00.29 erl_child_setup
dockerの各コンテナの負荷状況を確認する
docker上の各コンテナの負荷状況をリアルタイムに見るにはdocker stats
コマンドを使います。コンテナで運用している場合は、リクエストからレスポンスまで時間がかかった際にどのコンテナの負荷が上がっているか測定すると、ボトルネックが特定できそうです。
$ docker stats CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 8038defbab1e elixir-work 0.04% 34.47MiB / 3.855GiB 0.87% 1.25kB / 0B 21MB / 0B 22
※特定のNameを持つコンテナだけ見る場合は$ docker stats | grep コンテナ名
マシンの性能を測る
実際のマシンのパフォーマンスがどの程度か測るにはベンチマークを取るライブラリが便利です。実際に使っているマシンの性能がそもそも足りてないや、環境で差分が出た際にマシンスペックの差かどうかを判断することが出来ます。
例)UNIX BENCHの実行方法
$ wget https://byte-unixbench.googlecode.com/files/UnixBench5.1.3.tgz $ tar xvf UnixBench5.1.3.tgz $ cd UnixBench $ ./Run
参考: UnixBenchでベンチマーク - IDCF テックブログ
コードのパフォーマンスを測定する
実際にどこで発生したかわかってきたら実際にコードのパフォーマンスを測るとより具体的になります。printを使った手法、またrubyやrailsには標準でベンチマークを測定出来る機能があります。
標準出力で情報を見ることによって、コードのどの部分がボトルネックになっているのか特定することが出来ます。
今回は下記のようなコードを例に見ていきます。
resources = Hoge.where(fuga: piyo) objects = resources.map { |resource| fix_process(resource) } return objects def fix_process # something process end
標準出力で見る
上記のコードを解析するには下記のような標準出力を仕込めば下記処理について、おおよその実行時間を把握することが出来ます。
puts "=== START ===" puts "- Hoge.where(fuga: piyo)" puts Time.now resources = Hoge.where(fuga: piyo) p resouces puts Time.now puts "- resources.map { |resource| fix_process(resource) }" puts Time.now objects = resources.map { |resource| fix_process(resource) } p resouces puts Time.now puts "=== END ===" return objects def fix_process puts "- fix_process" puts Time.now # something process puts Time.now end
rubyのbenchmark機能を使う
rubyの標準のベンチマーク機能を使うと下記のようにBenchmark.bm
のブロックに渡した処理のベンチマークを取得出来ます。
require 'benchmark' puts "=== START ===" Benchmark.bm do |r| r("where") { resources = Hoge.where(fuga: piyo) } r("map") { objects = resources.map { |resource| fix_process(resource) } } end return objects def fix_process Benchmark.bm do |r| r("fix") { # something process } end end
これはサンプルですが下記のような結果を得られます。
user system total real test 0.000282 0.000478 0.000760 ( 5.020090)
ちなみに見方は下記の通りです。
条件を合わせる
ここは言わずもがなですが、なるべくパフォーマンスが劣化した環境と測定時は揃えた方が良いです。劣化時と状況が違ってしまうと何が原因かわからなくなってしまうので。。。
- 測定する項目(CPU、メモリ、実行時間)
- 実行時のTBLのレコード件数
- 処理の呼び出し方(引数等)
- config周りの設定
などなど...
おわりに
今回はパフォーマンス測定の心構えやTipsをまとめてみました。こういう言語によらないエンジニアとしての基礎知識が自分には不足しているので、学びがあったときはちゃんと整理して身につけられるようにしていきたい・・・!