はじめに

恥ずかしながら,パフォーマンスを意識したコードを書く,ということを今まであまりしてこなかったため,いい加減その辺意識を改めないと...と思うだけは思っている今日この頃.

そんな背景もあり,思うところもあったので,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] ); },
};

こちらもあわせてどうぞ

コメントをどうぞ