pockestrap

Programmer's memo

Gem として提供されているコマンドのベンチマーク

github.com

先程、RuboCop にパフォーマンス改善の Pull-Request を投げました。3%ぐらい速くなります。 この Pull-Request の効果を検証する際に用いたベンチマークの方法が、わりと面白くて便利(自画自賛)だったので、紹介します。

そもそもの問題

Gem で提供されているコマンドのベンチマークは多少めんどくさいです。 例えば、origin/master と作業中のブランチの両方で時間を計測したい場合、以下のようにする必要があります。
ここで、some_commandベンチマーク対象のコマンドだと思って下さい。

# origin/master でコマンドをインストール
$ git checkout origin/master
$ bundle exec rake install:local
# origin/master でベンチマーク
$ time some_command

# 作業中のブランチでコマンドをインストール
$ git checkout 作業中のブランチ
$ bundle exec rake install:local
# 作業中のブランチでベンチマーク
$ time some_command

このように、origin/master と作業中のブランチをいちいち行き来しなければならなくてとてもめんどくさいです。
インストールし直す手間があるため、スクリプトを用いた自動化もあまりやりやすくは無いでしょう。

解決方法

今回この問題を解決する為に取った方法を紹介します。

この問題は「ベンチマークを取るコマンドを切り替える度にインストールを行わないといけない」ことが問題です。
そのため、インストールを事前に行うアプローチを取ることにしました。

作業手順

ここで、some_command のバージョンは現在 1.0.0 であるとします。

# origin/master のコマンドを、'1.0.0.master' としてインストール
$ git checkout origin/master
$ vim some_command.gemspec # ここで、 spec.version = SomeCommand::VERSION のようになっているところを spec.version = SomeCommand::VERSION + '.master' のように書き換える。
$ bundle exec rake install:local

# 作業中のブランチのコマンドを、`1.0.0.wip' としてインストール
$ git checkout 作業中のブランチ
$ vim some_command.gemspec # ここで、 spec.version = SomeCommand::VERSION のようになっているところを spec.version = SomeCommand::VERSION + '.wip' のように書き換える。
$ bundle exec rake install:local

# origin/master でベンチマーク
$ time some_command _1.0.0.master_
# 作業中のブランチでベンチマーク
$ time some_command _1.0.0.wip_

gem でインストールされたコマンドは、第一引数に_version_という文字列を指定すると、指定したバージョンの物が実行されることを利用しています。
事前に origin/master のものを1.0.0.master, 作業中のものを1.0.0.wipとしてインストールし、ベンチマークの際にはそのバージョンを指定してコマンドを実行しています。

バージョン番号に.masterのような文字列が付いたものは、その文字列が付いていない番号より古いものと扱われます(.preなどを想像するとわかりやすいと思います)。 そのため、普段コマンドを利用する際にベンチマーク用にインストールしたコマンドを実行してしまう心配もありません。

また、この方式を利用すると、以下のようにスクリプトを使用してのベンチマークもやりやすくなります(これはRuboCopの例です)。

require 'benchmark'

def run_master
  system('rubocop _0.48.1.master_ --cache false --force-default-config > /dev/null')
end

def run_improvement
  system('rubocop _0.48.1.improvement_ --cache false --force-default-config > /dev/null')
end

Benchmark.bm(15) do |x|
  x.report('master'){     5.times{run_master}}
  x.report('improvement'){5.times{run_improvement}}
end

まとめ

Ruby で書かれたコマンドラインツールを速くしていこう💪🚀