単純データのシリアライズ速度比較

最近の CPAN モジュールで Storable を当たり前のように使っているのをみかけるのですが、入れ子になっていないフラットなハッシュ程度の単純なデータをシリアライズするには重いのではないかとベンチマークをとってみました。ついでに、以前から比較してみたかった pack/unpack の固定長と可変長の比較もやってみました。ベンチマークをとるのは、ハッシュのシリアライズでありがちな要素数の少ない文字列リストと、配列のシリアライズでありがちな要素数が多い整数リストの 2 つにしました。

結論

  • 素数が少ないハッシュのシリアライズには split/join が速いようです。
  • 素数が多い整数のリストのシリアライズには整数 unpack/pack が速いようです。
  • 可変長文字列 unpack/pack は split/join よりも遅いようです。
  • 単純なデータ構造では Storable のメリットは活きないようです。

素数の少ない文字列リスト

最も速かったのは split/join で、固定長文字列 unpack/pack 、可変長文字列 unpack/pack、Storable と続きます。可変長文字列 pack はネーティブ long とネットワークオーダー long に若干の差がある程度でほぼ同じと言って良いようです。Storable が一桁遅いのは体感的にも納得しますが、unpack/pack が split/join に負けるとは予想していなかったので意外な結果でした。

=head1 result
Benchmark: timing 100000 iterations
  split/join: ( 1.02 usr +  0.00 sys =  1.02 CPU) @ 98522.17/s
pack  (A16)*: ( 1.44 usr +  0.00 sys =  1.44 CPU) @ 69541.03/s
pack (l/A*)*: ( 1.88 usr +  0.00 sys =  1.88 CPU) @ 53304.90/s
pack (N/A*)*: ( 1.97 usr +  0.00 sys =  1.97 CPU) @ 50787.20/s
    Storable: (17.02 usr +  0.05 sys = 17.06 CPU) @  5860.63/s
=cut

use Benchmark qw(:all);
use Storable;
my $h = {title => 'hoge', author => 'fuga', body => 'hogehoge'};
timethese(100000, {
    'split/join' => sub { +{split /$;/, join $;, %$h} },
    'pack (A16)*' => sub { +{unpack '(A16)*', pack '(A16)*', %$h} },
    'pack (l/A*)*' => sub { +{unpack '(l/A*)*', pack '(l/A*)*', %$h} },
    'pack (N/A*)*' => sub { +{unpack '(N/A*)*', pack '(N/A*)*', %$h} },
    'Storable' => sub { Storable::thaw(Storable::freeze($h)) },
});

素数の多い整数のリスト

素数 1000 個の長い整数のリストのシリアライズも試してみました。今度は、整数 unpack/pack が 4 倍ほど他よりも速く、split/join、固定長文字列 unpack/pack、Storable はほぼ同じ程度みたいです。可変長文字列 unpack/pack は Storable よりも時間がかかりました。

=head1 result
Benchmark: timing 10000 iterations
     pack l*:  4 wallclock secs ( 3.58 usr +  0.00 sys =  3.58 CPU) @ 2794.86/s
     pack N*:  4 wallclock secs ( 4.28 usr +  0.00 sys =  4.28 CPU) @ 2335.36/s
  split/join: 11 wallclock secs (11.81 usr +  0.00 sys = 11.81 CPU) @  846.53/s
  pack (A8)*: 14 wallclock secs (13.91 usr +  0.01 sys = 13.92 CPU) @  718.34/s
    Storable: 17 wallclock secs (15.23 usr +  1.50 sys = 16.73 CPU) @  597.55/s
pack (l/A*)*: 26 wallclock secs (25.42 usr +  0.00 sys = 25.42 CPU) @  393.36/s
pack (N/A*)*: 26 wallclock secs (26.39 usr +  0.00 sys = 26.39 CPU) @  378.92/s
=cut

use Benchmark qw(:all);
use Storable;
$a = [1000..1999];
timethese(10000, {
    'split/join' => sub { [split /$;/, join $;, @$a] },
    'pack l*' => sub { [unpack 'l*', pack 'l*', @$a] },
    'pack N*' => sub { [unpack 'N*', pack 'N*', @$a] },
    'pack (l/A*)*' => sub { +{unpack '(l/A*)*', pack '(l/A*)*', @$a} },
    'pack (N/A*)*' => sub { +{unpack '(N/A*)*', pack '(N/A*)*', @$a} },
    'pack (A8)*' => sub { [unpack '(A8)*', pack '(A8)*', @$a] },
    'Storable' => sub { Storable::thaw(Storable::freeze($a)) },
});