Perlでベンチマーク
Perl Advent Calendar 2014の枠が空いていたので、ただのメモですが10日目の記事として晒すことに。
今さらかなり基本的なことだけど、Perlでのベンチマークの実行方法を調べて、適当にいろいろ試してみたメモ。
Benchmarkモジュール
基本的にはperldocを見れば良い。 ゆとりなのでPerldoc.jpにて。
いろいろ関数があるけども、よく使いそうな雰囲気なのは次の2つな気がした。
timethis
: 特定のコードの実行速度を測るcmpthese
: 複数のコードの実行速度を測りつつ比較する
timethis
DateTime
は遅いって言われているけど、実際にどれくらい遅いのか試しに測ってみる。
現在の時間を取得して1日足すという処理を例に。
timethis
の1つ目の引数は、2つ目の引数の処理を実行する回数を示している。
第2引数には、CodeRefかevalしたい文字列を指定する。
正の数を指定した場合は「実行回数」を、負の数を指定した場合は「最低実行時間」を示している。
0
はデフォルト値で、-3
を指定したときと同じ挙動を示す。
use Benchmark; use DateTimeX::Factory; my $dt = DateTimeX::Factory->new(time_zone => 'Asia/Tokyo'); timethis 0, sub { $dt->now->add(days => 1); };
実際に実行してみた結果が次の出力。 CPU時間3秒くらい使って9331回実行して、毎秒約3千回の速度で実行できたらしい。
timethis for 3: 3 wallclock secs ( 3.13 usr + 0.00 sys = 3.13 CPU) @ 2981.15/s (n=9331)
cmpthese
これだけだと、この処理にかかる時間がわかるだけで比較ができないし、実行環境によって速度も変わってくる。
ので、cmpthese
を使って比較を行ってみる。
cmpthese
では、1つ目の引数はtimethis
と同じで、2つ目の引数にはHashRefにて実行したいコードと、そのコードの名前を渡してあげる。
use Benchmark qw/ cmpthese /; use DateTimeX::Factory; use Time::Moment; my $dt = DateTimeX::Factory->new(time_zone => 'Asia/Tokyo'); cmpthese 0, { 'DateTime' => sub { $dt->now->add(days => 1) }, 'Time::Moment' => sub { Time::Moment->now->plus_days(1) }, };
実際に実行してみた結果が次の出力。 もはや、DateTimeが遅いとかじゃなくてTime::Moment速すぎワロタ状態。
Rate
の項目が実行速度を示していて、それより右側は他との比較を示している。
この例だと、Time::Moment
と名付けた処理は、DateTime
と名付けた処理よりも42690%速い、すなわち428倍の実行速度があることがわかる。
逆に、DateTime
はTime::Moment
よりも100%遅い(差が大きすぎて丸められてしまった!)、ことがわかる。
ちなみに、時間系モジュールの速度比較はここで詳しく行っている(Time::Moment
まじ速い)。
Rate DateTime Time::Moment DateTime 3034/s -- -100% Time::Moment 1298354/s 42690% --
適当にいろいろ測ってみる
ちらっとどこかで見かけた関数呼び出しのオーバーヘッドを測ってみる
このコードでお試し。
sub one { Time::Moment->now->plus_days(1) } sub two { one() } sub three { two() } cmpthese 0, { one => \&one, two => \&two, three => \&three, };
one
に元の処理を記述し、two
, three
では無駄に関数呼び出しを挟んでいる。
上記のTime::Moment
の処理を実行した場合、元の処理がかなり速いため、関数呼び出しのオーバーヘッドが如実に表れている。
自分の環境だと140000[1/sec] = 7[μsec]くらいが関数呼び出しにかかる時間らしい。
Rate three two one three 1047791/s -- -12% -22% two 1191842/s 14% -- -11% one 1338232/s 28% 12% --
次に、上記のDateTime
の処理で置き換えた版を実行してみる。
my $dt = DateTimeX::Factory->new(time_zone => 'Asia/Tokyo'); sub one { $dt->now->add(days => 1) } sub two { one() } sub three { two() } cmpthese 0, { one => \&one, two => \&two, three => \&three, };
当たり前だが、今度はどれも誤差レベルのしか生まれなかった。 関数の中身自体が遅く、呼び出し回数が少ない場合には、関数呼び出しのオーバーヘッドはさほど問題では無くなる。
Rate one three two one 2753/s -- -2% -3% three 2810/s 2% -- -1% two 2825/s 3% 1% --
シュワルツ変換によるソート速度向上を測定してみる
配列の各要素のsha512の16進数表現を文字列としてソートする場合を作って試した。 (そんな場合が実際にあるかはわからないが、単に重そうな処理をかませたかっただけ)
それぞれのラベルでは次の処理を行う
normal
: 愚直にsort
ブロック内でsha512_hex
を実行するschwartz
: シュワルツ変換で頑張る
use Benchmark qw/ cmpthese /; use Digest::SHA qw/ sha512_hex /; my @array = (1..100); cmpthese 0, { normal => sub { my @sorted = sort {sha512_hex($a) cmp sha512_hex($b)} @array; }, schwartz => sub { my @sorted = map {$_->[1]} sort {$a->[0] cmp $b->[0]} map {[sha512_hex($_), $_]} @array; }, };
この場合、シュワルツ変換を使った方が約7倍速くなった。
Rate normal schwartz normal 305/s -- -86% schwartz 2186/s 617% --
ソート対象の配列の要素数を100個から10個に減らした場合でも、まだまだシュワルツ変換を使った方が3倍速く、有効そう。
Rate normal schwartz normal 7590/s -- -67% schwartz 23057/s 204% --
こんな感じで
実行速度が実際にどれくらいなのか、AとBの実装ではどれくらい速度差があるのか、、、などを知りたいときは、ちゃんとベンチマークを実行し、数字ベースで比較できると良いな、という感想。