はじめに
恥ずかしながら,パフォーマンスを意識したコードを書く,ということを今まであまりしてこなかったため,いい加減その辺意識を改めないと...と思うだけは思っている今日この頃.
そんな背景もあり,思うところもあったので,Perlにおけるパフォーマンス測定ツールではメジャーっぽい Benchmark モジュールをちょこっとだけ使ってみました.
使ってみた関数
今回は timethese関数や cmpthese関数を使ってみました.
use Benchmark qw( :all ); my $results = timethese( $count, { name1 => sub { ... }, name2 => sub { ... }, } ); cmpthese( $results );
引数の与え方も直感的です.
計測の対象
hoge_fuga_piyo hoge-hoge_foo-bar-baz_fuga_piyo-piyo
といった文字列を
Hoge::Fuga::Piyo HogeHoge::FooBarBaz::Fuga::PiyoPiyo
といった感じにそれぞれ置き換えるためのスクリプトを 3パタン+α用意しました.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | sub a { join '::', map { join '', map { ucfirst; } split '-'; } split '_', shift; } sub b { join '::', map { join '', map { ucfirst; } split '-', $_; } split '_', shift; } sub c { my @a = (); for( split '_', shift ) { my @b = (); for( split '-' ) { push @b, ucfirst; } push @a, join '', @b; } join '::', @a; } sub d { my @a = (); for my $a ( split '_', shift ) { my @b = (); for my $b ( split '-', $a ) { push @b, ucfirst $b; } push @a, join '', @b; } join '::', @a; } sub e { my $str = shift; $str =~ s{^(\w)}{uc $1;}ex; $str =~ s{(_\w)}{uc $1;}gex; $str =~ s{_}{::}g; $str =~ s{-(\w)}{uc $1;}gex; $str =~ s{-}{}g; $str; } sub f { ( my $str = shift ) =~ s{(^|_)(\w)}{($1 ? '::' : '' ) . uc $2}gex; $str =~ s{-(\w)}{uc $1;}gex; $str; } |
実行環境
ThinkPad R51 で実行しました.詳しくは次のとおりです.
- Debian GNU/Linux etch-and-a-half @ VMware Server on Windows XP SP3
- Pentium M 1.73GHz
- 560MB (VM) / 2GB (Total)
- Perl 5.8.8
実行結果
先のスクリプトたちをそれぞれ 1,000,000回実行させてみた結果,次のようになりました.
/home/www/test/perl/20090207 % ./01.pl 1_000_000
hoge_fuga_piyo
a: Hoge::Fuga::Piyo
b: Hoge::Fuga::Piyo
c: Hoge::Fuga::Piyo
d: Hoge::Fuga::Piyo
e: Hoge::Fuga::Piyo
f: Hoge::Fuga::Piyo
Benchmark: timing 1000000 iterations of a0, b0, c0, d0, e0, f0...
a0: 9 wallclock secs ( 9.49 usr + 0.00 sys = 9.49 CPU) @ 105374.08/s (n=1000000)
b0: 9 wallclock secs ( 9.42 usr + 0.00 sys = 9.42 CPU) @ 106157.11/s (n=1000000)
c0: 15 wallclock secs (14.14 usr + 0.00 sys = 14.14 CPU) @ 70721.36/s (n=1000000)
d0: 15 wallclock secs (13.55 usr + 0.00 sys = 13.55 CPU) @ 73800.74/s (n=1000000)
e0: 11 wallclock secs (11.66 usr + 0.00 sys = 11.66 CPU) @ 85763.29/s (n=1000000)
f0: 12 wallclock secs (10.84 usr + 0.00 sys = 10.84 CPU) @ 92250.92/s (n=1000000)
Rate c0 d0 e0 f0 a0 b0
c0 70721/s -- -4% -18% -23% -33% -33%
d0 73801/s 4% -- -14% -20% -30% -30%
e0 85763/s 21% 16% -- -7% -19% -19%
f0 92251/s 30% 25% 8% -- -12% -13%
a0 105374/s 49% 43% 23% 14% -- -1%
b0 106157/s 50% 44% 24% 15% 1% --
hoge-hoge_foo-bar-baz_fuga_piyo-piyo
a: HogeHoge::FooBarBaz::Fuga::PiyoPiyo
b: HogeHoge::FooBarBaz::Fuga::PiyoPiyo
c: HogeHoge::FooBarBaz::Fuga::PiyoPiyo
d: HogeHoge::FooBarBaz::Fuga::PiyoPiyo
e: HogeHoge::FooBarBaz::Fuga::PiyoPiyo
f: HogeHoge::FooBarBaz::Fuga::PiyoPiyo
Benchmark: timing 1000000 iterations of a1, b1, c1, d1, e1, f1...
a1: 18 wallclock secs (16.86 usr + 0.00 sys = 16.86 CPU) @ 59311.98/s (n=1000000)
b1: 17 wallclock secs (17.12 usr + 0.00 sys = 17.12 CPU) @ 58411.21/s (n=1000000)
c1: 24 wallclock secs (23.04 usr + 0.00 sys = 23.04 CPU) @ 43402.78/s (n=1000000)
d1: 22 wallclock secs (22.42 usr + 0.00 sys = 22.42 CPU) @ 44603.03/s (n=1000000)
e1: 22 wallclock secs (21.45 usr + 0.01 sys = 21.46 CPU) @ 46598.32/s (n=1000000)
f1: 25 wallclock secs (24.82 usr + 0.00 sys = 24.82 CPU) @ 40290.09/s (n=1000000)
Rate f1 c1 d1 e1 b1 a1
f1 40290/s -- -7% -10% -14% -31% -32%
c1 43403/s 8% -- -3% -7% -26% -27%
d1 44603/s 11% 3% -- -4% -24% -25%
e1 46598/s 16% 7% 4% -- -20% -21%
b1 58411/s 45% 35% 31% 25% -- -2%
a1 59312/s 47% 37% 33% 27% 2% --結果を眺めてみて
これらの中では,split して map して join する方法がいちばんよさげ,というのが数値でわかることができますね.(もっとも遅いものの 5割増しくらい)
このことが実用時にどれくらいの差になってくるのかはサッパリわかっていませんが ><
おわりに
以上,Benchmarkモジュールを使って,同じ結果を返す数パタンのスクリプトを対象に,パフォーマンスを測定する練習をしてみました.
こういったツールを使いこなすことでボトルネックとなるような部分を見出し,より速い(とりあえずは自分比で)コードを書けるように精進したいものです.可読性にも気をつけたいですね.
それでは,今回の計測のために書いたスクリプトの全体を次節に載せて終わりにするとします.
ソースコードひととおり
計測スクリプト全体のソースコードは次のとおりです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | #!/usr/bin/perl use strict; use warnings; use Benchmark qw( :all ); my $COUNT = eval( $ARGV[0] ) || 10_000; sub a { join '::', map { join '', map { ucfirst; } split '-'; } split '_', shift; } sub b { join '::', map { join '', map { ucfirst; } split '-', $_; } split '_', shift; } sub c { my @a = (); for( split '_', shift ) { my @b = (); for( split '-' ) { push @b, ucfirst; } push @a, join '', @b; } join '::', @a; } sub d { my @a = (); for my $a ( split '_', shift ) { my @b = (); for my $b ( split '-', $a ) { push @b, ucfirst $b; } push @a, join '', @b; } join '::', @a; } sub e { my $str = shift; $str =~ s{^(\w)}{uc $1;}ex; $str =~ s{(_\w)}{uc $1;}gex; $str =~ s{_}{::}g; $str =~ s{-(\w)}{uc $1;}gex; $str =~ s{-}{}g; $str; } sub f { ( my $str = shift ) =~ s{(^|_)(\w)}{($1 ? '::' : '' ) . uc $2}gex; $str =~ s{-(\w)}{uc $1;}gex; $str; } sub printx { printf "%s\n", sprintf shift || '', @_; } my @str = ( 'hoge_fuga_piyo', 'hoge-hoge_foo-bar-baz_fuga_piyo-piyo', ); # # # printx $str[0]; eval( sprintf q{printx '%s: %%s', %s( '%s' )}, $_, $_, $str[0] ) for 'a' .. 'f'; printx; cmpthese timethese $COUNT, { a0 => sub { a( $str[0] ); }, b0 => sub { b( $str[0] ); }, c0 => sub { c( $str[0] ); }, d0 => sub { d( $str[0] ); }, e0 => sub { e( $str[0] ); }, }; printx for 1 .. 2; # # # printx $str[1]; eval( sprintf q{printx '%s: %%s', %s( '%s' )}, $_, $_, $str[1] ) for 'a' .. 'f'; printx; cmpthese timethese $COUNT, { a1 => sub { a( $str[1] ); }, b1 => sub { b( $str[1] ); }, c1 => sub { c( $str[1] ); }, d1 => sub { d( $str[1] ); }, e1 => sub { e( $str[1] ); }, }; |
