avgtime - コマンドの実行時間を手軽に計測するユーティリティ

目的

コマンドの実行時間を手軽に測りたい。

  • 処理時間が短い場合のために、コマンドを複数回まとめて実行した結果を計測する
  • システム状態によるバラツキを考慮して、複数回の試行の平均を取る(その際、最大値と最小値を除く)

結果

上記仕様を満たすユーティリティをRubyで作成した。

avgtime
https://sssvn.jp/svn/spikelet/utils/avgtime

以下、詳細。

zsh で "time repeat ..." が測定できない

コマンドを複数回実行して時間を測るのだから、time + repeat でいけるだろうと試したところ、

% time repeat 100 ls
....

lsは100回実行されているが、timeの結果が出力されない。

repeatを外せば、timeを表示する。

% time ls
ls  0.00s user 0.00s system 0% cpu 0.002 total

ちなみに手元の環境は zsh-4.2.0 だが、最新の zsh-4.2.7 でも同じ結果になる。

zshall(1)を調べると、

  • time は引数に pipeline を取る
  • pipeline とは、単独の simple command か、simple command を '|' か '|&' で接続したもの
  • repeat は complex command に分類され、simple command ではない(他に if, for, while, until, case, select, sub-shell実行が該当)

となっており、"time repeat" というシーケンスは使えないようだ。

ということは、検索するか、自作するしか無い。
この手のちょっとしたツールは、探すよりも作った方が早い(経験則)。

スクリプトで作る

Rubyには、Process.time メソッドがある(http://www.ruby-lang.org/ja/man/html/Process.html#Process.2etimes:titie)。
これを利用して、script内でコマンドのloop実行、かつ複数回試行して平均を計算する、という内容にした。

avgtime
https://sssvn.jp/svn/spikelet/utils/avgtime
#!/usr/bin/env ruby
# count processor times of sub-command in average, without min and max.

def die(msg)
  STDERR.puts msg
  exit 0
end

##
try_times = ARGV.shift.to_i
loop_count = ARGV.shift.to_i
command = ARGV.join " "

die "usage: #$0 try-times loop-count command..." end if ARGV.length<3
die "try-times must be greater than 2." if try_times<=2
die "loop-count must be greater than 0." if loop_count<=0

used_times = []
try_times.times{ |n|
  prev = Process.times
  loop_count.times{ system command }
  cur = Process.times
  passage = cur.cutime - prev.cutime + cur.cstime - prev.cstime
  STDERR.puts "try #{n+1}: #{passage}"
  used_times.push passage
}

max = used_times.max
min = used_times.min
total = 0
used_times.each{ |e| total += e }
average = (total - max - min) / (try_times - 2)
STDERR.puts "average: #{average}"

測定結果は、stderr に出すようにした(コマンド自体の出力と混乱しないように)。

% avgtime 5 1 sleep 1 >/dev/null
try 1: 0.0
try 2: 0.0
try 3: 0.0
try 4: 0.0
try 5: 0.0
average: 0.0

% avgtime 5 1000 ls >/dev/null
try 1: 1.91
try 2: 1.91
try 3: 1.92
try 4: 1.88
try 5: 1.88
average: 1.88666666666667

% avgtime 5 1000 'echo "" | gcc -c -x c -'
try 1: 21.41
try 2: 21.39
try 3: 21.46
try 4: 21.43
try 5: 21.44
average: 21.4033333333333

おまけ:excel の TRIMMEAN関数

同じような処理をする関数が、エクセルにあるらしい(http://office.microsoft.com/ja-jp/excel/HP052093221041.aspx)。
意外なところで使えそうなので、備忘録。

追記: Benchmarkライブラリ

rubyの標準ライブラリに、Benchmarkクラスというものがあるのを知った(プログラミング言語 Ruby リファレンスマニュアル)。

まあ、似たようなことをしてくれるのだが、複数回試行して平均を出すというのは無いようなので(Benchmark.bmbmでリハーサル機能はあるが、別集計になる)、avgtimeもこのまま残しておこう。

ところでBenchmark::Tmsクラスは四則演算を定義してるが、Benchmark.bmbmのリハーサルでのtotalの積算にしか使ってないみたいだ。RDocの中で使用例として書いているぐらいか。
比較用メソッド('>'や'max'など)があれば、avgtimeと同じことをもっと簡単にできるのだがなあ。