Madogiwa Blog

主に技術系の学習メモに使っていきます。

パフォーマンス測定の心構えとTipsのMEMO

最近、パフォーマンス測定について学びがあったので基本的な内容が多いですが、自戒もこめてまとめておきますm( )m

大事なこと

  • 思い込みを捨てる
    • あたりをつけてそこから測定するのは大事だが、迷ったときにはここでは起きないはずといった思い込みは捨てて、ちゃんと網羅的に測定する。
  • ボトルネック、発生箇所を場所を測定し特定する
    • どこで発生しているのか、ボトルネックはどこなのかをパフォーマンス等を定量的に測り特定する。
  • 条件を合わせる
    • ローカルで同様の処理を実行して試す等を行う際は、実際に発生した状況との乖離をなるべく少なくする。
    • 複数の対策を比較するときは検証方法や前提を揃えて測定する。

思い込みを捨てる

例えばどこかしらの処理でパフォーマンスの起きてる場合には、根拠を持ってあたりを付けて着手する(N+1が発生してそうな処理なので、そこがパフォーマンス劣化の原因になっていそう等)のは時間短縮的にも望ましいと思いますが、道に迷ったときには、全体像を思い浮かべて網羅的・定量的に測定してボトルネックを探すことが大事。

例) リクエストからレスポンスが返却されるまで(サーバー単位)

f:id:madogiwa0124:20190811130920p:plain

例) URL解決から結果が生成されるまで(Rails)

f:id:madogiwa0124:20190811130959p:plain

ボトルネック、発生箇所を場所を測定し特定する

コードを見てもパフォーマンスが遅くなっていそうな場所が見当たらない等の場合はシステムのパフォーマンスを測定して、どのような場所に負荷がかかっているのかを測定すると、どのような処理が影響してそうか判断するヒントになります。(コードではなく他のプロセスがサーバー上の負荷を上げてただけ等の事象にも気づけそうです。)

迷ったときはシステムで大まかに測定し、その結果をもとにコードを見ていくと答えが見つかるかもしれません。

システムのパフォーマンスを測定する

システムのパフォーマンスを測るために使えそうな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を使った手法、またrubyrailsには標準でベンチマークを測定出来る機能があります。

標準出力で情報を見ることによって、コードのどの部分がボトルネックになっているのか特定することが出来ます。

今回は下記のようなコードを例に見ていきます。

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)

ちなみに見方は下記の通りです。

  • user: rubyプログラムの実行時間
  • system: ファイルの読み書き等のシステムコールの実行時間
  • real: 実際の経過時間

参考: Ruby でベンチマークを取る方法 - Qiita

条件を合わせる

ここは言わずもがなですが、なるべくパフォーマンスが劣化した環境と測定時は揃えた方が良いです。劣化時と状況が違ってしまうと何が原因かわからなくなってしまうので。。。

  • 測定する項目(CPU、メモリ、実行時間)
  • 実行時のTBLのレコード件数
  • 処理の呼び出し方(引数等)
  • config周りの設定

などなど...

おわりに

今回はパフォーマンス測定の心構えやTipsをまとめてみました。こういう言語によらないエンジニアとしての基礎知識が自分には不足しているので、学びがあったときはちゃんと整理して身につけられるようにしていきたい・・・!