4
4
use Benchmark qw(cmpthese :hireswallclock);
5
use Sereal::Decoder qw(decode_sereal);
6
use Sereal::Encoder qw(encode_sereal);
7
use JSON::XS qw(decode_json encode_json);
5
use Sereal::Decoder qw(decode_sereal sereal_decode_with_object);
6
use Sereal::Encoder qw(encode_sereal sereal_encode_with_object);
8
7
use Storable qw(nfreeze thaw);
9
use Data::Undump qw(undump);
10
8
use Data::Dumper qw(Dumper);
11
use Data::Dumper::Limited qw(DumpLimited);
12
use Data::MessagePack;
13
use CBOR::XS qw(encode_cbor decode_cbor);
14
11
use Getopt::Long qw(GetOptions);
33
'duration=f' => \($duration=-3),
34
'encoder' => \$encoder,
35
'decoder' => \$decoder,
37
'tiny' => \$tiny_data,
38
'small' => \$small_data,
39
'medium' => \$medium_data,
40
'large' => \$large_data,
41
'very_large|very-large|verylarge' => \$very_large_data,
42
'no_bless|no-bless|nobless' => \$nobless,
43
'sereal_only|sereal-only|serealonly' => \$sereal_only,
44
'diagrams' => \$diagrams,
45
'diagram_output=s' => \$diagram_output_dir,
47
eval "sub SEREAL_ONLY () { $sereal_only }";
50
my $fail = do {no warnings; $tiny_data + $small_data + $medium_data + $very_large_data + $large_data - 1};
51
if ($fail and $fail > 0) {
15
'secs|duration=f' => \( my $duration = -5 ),
16
'encoder' => \( my $encoder = 0 ),
17
'decoder' => \( my $decoder = 0 ),
18
'dump|d' => \( my $dump = 0 ),
19
'only=s@' => \( my $only = undef ),
20
'exclude=s@' => \( my $exclude = undef ),
21
'tiny' => \( my $tiny_data = 0 ),
22
'small' => \( my $small_data = 0 ),
23
'medium' => \( my $medium_data = 0 ),
24
'large' => \( my $large_data = 0 ),
25
'very_large|very-large|verylarge' => \( my $very_large_data = 0 ),
26
'no_bless|no-bless|nobless' => \( my $nobless = 0 ),
27
'sereal_only|sereal-only|serealonly' => \( my $sereal_only = 0 ),
28
'diagrams' => \( my $diagrams = 0 ),
29
'diagram_output=s' => \( my $diagram_output_dir = "" ),
30
) or die "Bad option";
33
$tiny_data + $small_data + $medium_data + $very_large_data + $large_data - 1;
34
if ( $fail and $fail > 0 ) {
52
35
die "Only one of --tiny, --small, --medium, --large, --very-large allowed!";
54
37
$encoder = 1 if not $encoder and not $decoder;
58
our $mpo = Data::MessagePack->new();
62
my $chars = join("", "a".."z", "A".."Z") x 2;
44
my $chars = join( "", "a" .. "z", "A" .. "Z" ) x 2;
64
push @str, substr($chars, int(rand(int(length($chars)/2+1))), 10) for 1..1000;
65
my @rand = map rand, 1..1000;
46
push @str, substr( $chars, int( rand( int( length($chars) / 2 + 1 ) ) ), 10 )
48
my @rand = map rand, 1 .. 1000;
52
$enc_snappy, $dec_snappy,
53
$enc_zlib_fast, $dec_zlib_fast,
54
$enc_zlib_small, $dec_zlib_small,
55
$jsonxs, $msgpack, $dd_noindent, $dd_indent, $cbor
57
my $storable_tag= "strbl";
58
my $sereal_tag= "srl";
61
enc => '$::jsonxs->encode($data);',
62
dec => '$::jsonxs->decode($encoded);',
63
name => 'JSON::XS OO',
65
$jsonxs = JSON::XS->new()->allow_nonref();
67
use => 'use JSON::XS qw(decode_json encode_json);',
70
enc => 'DumpLimited($data);',
71
dec => 'Data::Undump::undump($encoded);',
72
name => 'Data::Dump::Limited',
74
'use Data::Undump qw(undump);',
75
'use Data::Dumper::Limited qw(DumpLimited);',
79
enc => '$::msgpack->pack($data);',
80
dec => '$::msgpack->unpack($encoded);',
81
name => 'Data::MsgPack',
82
use => 'use Data::MessagePack;',
84
$msgpack = Data::MessagePack->new();
88
enc => '$::cbor->encode($data);',
89
dec => '$::cbor->decode($encoded);',
91
use => 'use CBOR::XS qw(encode_cbor decode_cbor);',
93
$cbor= CBOR::XS->new();
97
enc => 'Data::Dumper->new([$data])->Indent(0)->Dump();',
98
dec => 'eval $encoded;',
99
name => 'Data::Dumper no-indent',
102
enc => 'Dumper($data);',
103
dec => 'eval $encoded;',
104
name => 'Data::Dumper indented',
107
enc => 'nfreeze($data);',
108
dec => 'thaw($encoded);',
112
enc => 'encode_sereal($data, $opt);',
113
dec => 'decode_sereal($encoded, $opt);',
114
name => 'Sereal functional',
117
enc => 'sereal_encode_with_object($::enc,$data);',
118
dec => 'sereal_decode_with_object($::dec,$encoded);',
119
name => 'Sereal functional with object',
122
enc => '$::enc->encode($data);',
123
dec => '$::dec->decode($encoded);',
126
$enc = Sereal::Encoder->new( %opt ? \%opt : () );
127
$dec = Sereal::Decoder->new( \%opt ? \%opt : () );
131
enc => '$::enc_snappy->encode($data);',
132
dec => '$::dec_snappy->decode($encoded);',
133
name => 'Sereal OO snappy',
135
$enc_snappy = Sereal::Encoder->new(
138
compress => Sereal::Encoder::SRL_SNAPPY
141
$dec_snappy = Sereal::Decoder->new( %opt ? \%opt : () );
145
enc => '$::enc_zlib_fast->encode($data);',
146
dec => '$::dec_zlib_fast->decode($encoded);',
147
name => 'Sereal OO zlib fast',
149
$enc_zlib_fast = Sereal::Encoder->new(
152
compress => Sereal::Encoder::SRL_ZLIB,
154
compress_threshold => 0,
157
$dec_zlib_fast = Sereal::Decoder->new( %opt ? \%opt : () );
161
enc => '$::enc_zlib_small->encode($data);',
162
dec => '$::dec_zlib_small->decode($encoded);',
163
name => 'Sereal OO zib best',
165
$enc_zlib_small = Sereal::Encoder->new(
168
compress => Sereal::Encoder::SRL_ZLIB,
169
compress_level => 10,
170
compress_threshold => 0,
173
$dec_zlib_small = Sereal::Decoder->new( %opt ? \%opt : () );
178
my @pat= map { split /\s*,\s*/, $_ } @$only;
180
foreach my $key (keys %meta) {
181
$key=~/$_/ and $only->{$key}= 1
184
die "Only [@pat] produced no matches!" unless keys %$only;
187
my @pat= map { split /\s*,\s*/, $_ } @$exclude;
189
foreach my $key (keys %meta) {
190
$key=~/$_/ and $exclude->{$key}= 1
193
die "Exclude [@pat] produced no matches!" unless keys %$exclude;
68
$data{$_}= make_data() for qw(sereal sereal_func dd1 dd2 ddl mp json_xs storable sereal_snappy sereal_zlib_fast sereal_zlib_small cbor);
70
our $enc = Sereal::Encoder->new(\%opt);
71
our $enc_snappy = Sereal::Encoder->new({%opt, compress => Sereal::Encoder::SRL_SNAPPY});
72
our $enc_zlib_fast = Sereal::Encoder->new({%opt, compress => Sereal::Encoder::SRL_ZLIB, compress_level => 1, compress_threshold => 0});
73
our $enc_zlib_small = Sereal::Encoder->new({%opt, compress => Sereal::Encoder::SRL_ZLIB, compress_level => 10, compress_threshold => 0});
74
our $dec = Sereal::Decoder->new(\%opt);
76
our ($json_xs, $dd1, $dd2, $ddl, $sereal, $storable, $mp, $sereal_snappy, $sereal_zlib_fast, $sereal_zlib_small, $cbor);
77
# do this first before any of the other dumpers "contaminate" the iv/pv issue
78
$sereal = $enc->encode($data{sereal});
79
$sereal_snappy = $enc_snappy->encode($data{sereal_snappy});
80
$sereal_zlib_fast = $enc_zlib_fast->encode($data{sereal_zlib_fast});
81
$sereal_zlib_small = $enc_zlib_small->encode($data{sereal_zlib_small});
83
$json_xs = encode_json($data{json_xs}) if !$medium_data or $nobless;
84
$dd1 = Data::Dumper->new([$data{dd1}])->Indent(0)->Dump();
85
$dd2 = Dumper($data{dd2});
86
$ddl = DumpLimited($data{ddl}) if !$medium_data or $nobless;
87
$mp = $mpo->pack($data{mp}) if !$medium_data or $nobless;
88
$cbor = encode_cbor($data{cbor}) if !$medium_data or $nobless;
89
$storable = nfreeze($data{storable});
201
foreach my $key ( sort keys %meta ) {
202
my $info = $meta{$key};
204
next if $only and not $only->{$key} and $key ne $storable_tag;
205
next if $exclude and $exclude->{$key} and $key ne $storable_tag;
206
if (my $use= $info->{use}) {
207
$use= [$use] unless ref $use;
208
$use= join ";\n", @$use, 1;
210
warn "Can't load dependencies for $info->{name}, skipping\n";
214
$info->{enc}=~s/\$data/\$::data{$key}/g;
215
$info->{dec}=~s/\$encoded/\$::encoded{$key}/g;
216
$info->{enc}=~s/\$opt/%opt ? "\\%::opt" : ""/ge;
217
$info->{dec}=~s/\$opt/%opt ? "\\%::opt" : ""/ge;
219
$data{$key} = make_data();
220
$info->{init}->() if $info->{init};
221
$encoded{$key} = eval $info->{enc}
222
or die "Failed to eval $info->{enc}: $@";
223
$decoded{$key} = eval '$::x = ' . $info->{dec} . '; 1'
224
or die "Failed to eval $info->{dec}: $@\n$encoded{$key}\n";
225
$info->{size} = bytes::length( $encoded{$key} );
226
next if $only and not $only->{$key};
227
next if $exclude and $exclude->{$key};
228
$enc_bench{$key} = '$::x_' . $key . ' = ' . $info->{enc};
229
$dec_bench{$key} = '$::x_' . $key . ' = ' . $info->{dec};
232
my $sereal = $encoded{$sereal_tag};
91
233
print($sereal), exit if $dump;
93
my $sereal_len= bytes::length($sereal);
98
(($medium_data && !$nobless) ? () : (
99
["JSON::XS", bytes::length($json_xs)],
100
["Data::Dumper::Limited", bytes::length($ddl)],
101
["Data::MessagePack", bytes::length($mp)],
102
["CBOR", bytes::length($cbor)],
104
["Data::Dumper (1)", bytes::length($dd1)],
105
["Data::Dumper (2)", bytes::length($dd2)],
106
["Storable", bytes::length($storable)],
107
["Sereal::Encoder", bytes::length($sereal)],
108
["Sereal::Encoder, Snappy", bytes::length($sereal_snappy)],
109
["Sereal::Encoder, Zlib (fast)", bytes::length($sereal_zlib_fast)],
110
["Sereal::Encoder, Zlib (small)", bytes::length($sereal_zlib_small)],
112
for my $tuple (@size_datasets) {
113
my ($name, $size) = @$tuple;
114
printf "%-40s %12d bytes %.2f%% of sereal\n", $name, $size, $size/$sereal_len *100;
235
my $storable_len = bytes::length($encoded{$storable_tag});
237
sort { $a->{size} <=> $b->{size} || $a->{name} cmp $b->{name} }
238
grep { defined $_->{size} }
241
next unless $info->{size};
242
if ($info->{tag} eq $storable_tag) {
243
printf "%-40s %12d bytes\n",
244
$info->{name} . " ($info->{tag})", $info->{size};
246
printf "%-40s %12d bytes %6.2f%% of $storable_tag\n",
247
$info->{name} . " ($info->{tag})", $info->{size},
248
$info->{size} / $storable_len * 100;
119
my ($encoder_result, $decoder_result);
253
my ( $encoder_result, $decoder_result );
121
$encoder_result = cmpthese(
126
($medium_data && !$nobless ? () : (
127
json_xs => '$::x = encode_json($::data{json_xs});',
128
ddl => '$::x = DumpLimited($::data{ddl});',
129
msgpack => '$::x = $::mpo->pack($::data{mp});',
130
cbor => '$::x = encode_cbor($::data{cbor});',
132
dd_noindent => '$::x = Data::Dumper->new([$::data{dd1}])->Indent(0)->Dump();',
133
dd => '$::x = Dumper($::data{dd2});',
134
storable => '$::x = nfreeze($::data{storable});',
136
sereal_func => '$::x = encode_sereal($::data{sereal_func}, \%::opt);',
137
sereal => '$::x = $::enc->encode($::data{sereal});',
138
sereal_snappy => '$::x = $::enc_snappy->encode($::data{sereal_snappy});',
139
sereal_zlib_fast => '$::x = $::enc_zlib_fast->encode($::data{sereal_zlib_fast});',
140
sereal_zlib_small => '$::x = $::enc_zlib_small->encode($::data{sereal_zlib_small});',
255
print "\n* Timing encoders\n";
256
$encoder_result = cmpthese( $duration, \%enc_bench );
146
$decoder_result = cmpthese(
151
($medium_data && !$nobless ? () : (
152
json_xs => '$::x = decode_json($::json_xs);',
153
undump_ddl => '$::x = Data::Undump::undump($::ddl);',
154
msgpack => '$::x = $::mpo->unpack($::mp);',
155
cbor => '$::x = decode_cbor($::cbor);',
157
eval_dd => '$::x = eval $::dd1;',
158
storable => '$::x = thaw($::storable);',
160
sereal_func => '$::x = decode_sereal($::sereal, \%::opt);',
161
sereal => '$::x = $::dec->decode($::sereal);',
162
sereal_snappy => '$::x = $::dec->decode($::sereal_snappy);',
163
sereal_zlib_fast => '$::x = $::dec->decode($::sereal_zlib_fast);',
164
sereal_zlib_small => '$::x = $::dec->decode($::sereal_zlib_small);',
260
print "\n* Timing decoders\n";
261
$decoder_result = cmpthese( $duration, \%dec_bench );
174
269
elsif ($small_data) {
175
270
$data_set_name = "small hash";
176
return { foo=> 1, bar => [100,101,102], str => "this is a \x{df} string which has to be serialized" };
273
bar => [ 100, 101, 102 ],
274
str => "this is a \x{df} string which has to be serialized"
178
277
elsif ($medium_data) {
180
{ foo => 1, bar => [100,101,102], str => "this is a \x{df} string which has to be serialized" },
181
{ foo => 2, bar => [103,103,106,999], str2 => "this is a \x{df} aaaaaastring which has to be serialized" },
182
{ foozle => 3, bar => [100], str3 => "this is a \x{df} string which haaaaadsadas to be serialized" },
183
{ foozle => 3, bar => [], st4r => "this is a \x{df} string which has to be sdassdaerialized" },
184
{ foo => 1, bar => [100,101,102], s5tr => "this is a \x{df} string which has to be serialized" },
185
{ foo => 2, bar => [103,103,106,999], str => "this is a \x{df} aaaaaastring which has to be serialized" },
186
{ foozle => 3, bar => [100], str => "this is a \x{df} string which haaaaadsadas to be serialized" },
187
{ foozle => 3, bar => [], str2 => "this is a \x{df} string which has to be sdassdaerialized" },
188
{ foo2 => -99999, bar => [100,101,102], str2 => "this is a \x{df} string which has to be serialized" },
189
{ foo2 => 213, bar => [103,103,106,999], str => "this is a \x{df} aaaaaastring which has to be serialized" },
190
{ foozle2 => undef, bar => [100], str => "this is a \x{df} string which haaaaadsadas to be serialized" },
191
{ foozle2 => undef, bar => [1..20], str => "this is a \x{df} string which has to be sdassdaerialized" },
281
bar => [ 100, 101, 102 ],
282
str => "this is a \x{df} string which has to be serialized"
286
bar => [ 103, 103, 106, 999 ],
288
"this is a \x{df} aaaaaastring which has to be serialized"
294
"this is a \x{df} string which haaaaadsadas to be serialized"
300
"this is a \x{df} string which has to be sdassdaerialized"
304
bar => [ 100, 101, 102 ],
305
s5tr => "this is a \x{df} string which has to be serialized"
309
bar => [ 103, 103, 106, 999 ],
311
"this is a \x{df} aaaaaastring which has to be serialized"
317
"this is a \x{df} string which haaaaadsadas to be serialized"
323
"this is a \x{df} string which has to be sdassdaerialized"
327
bar => [ 100, 101, 102 ],
328
str2 => "this is a \x{df} string which has to be serialized"
332
bar => [ 103, 103, 106, 999 ],
334
"this is a \x{df} aaaaaastring which has to be serialized"
340
"this is a \x{df} string which haaaaadsadas to be serialized"
346
"this is a \x{df} string which has to be sdassdaerialized"
193
349
my @classes = qw(Baz Baz Baz3 Baz2 Baz Baz Baz3 Baz2 Baz Baz Baz3 Baz2);
195
bless($obj[$_], $classes[$_]) for 0..$#obj;
351
$data_set_name = "array of small hashes with relations";
354
bless( $obj[$_], $classes[$_] ) for 0 .. $#obj;
196
355
$data_set_name = "array of small objects with relations";
199
$data_set_name = "array of small hashes with relations";
201
foreach my $i (1..$#obj) {
202
$obj[$i]->{parent} = $obj[$i-1];
357
foreach my $i ( 1 .. $#obj ) {
358
$obj[$i]->{parent} = $obj[ $i - 1 ];
206
elsif ($very_large_data) { # "large data"
362
elsif ($very_large_data) { # "large data"
207
363
$data_set_name = "really rather large data structure";
209
[1..10000], {@str}, {@str}, [1..10000],
366
{@str}, {@str}, [ 1 .. 10000 ],
210
367
{@str}, [@rand], {@str}, {@str},
213
\@refs, \@refs, [map {[reverse 1..100]} (0..1000)], [map {+{foo => "bar", baz => "buz"}} 1..2000]
371
[ map { [ reverse 1 .. 100 ] } ( 0 .. 1000 ) ],
372
[ map { +{ foo => "bar", baz => "buz" } } 1 .. 2000 ]
216
else { # "large data"
375
else { # "large data"
217
376
$data_set_name = "large data structure";
219
[1..10000], {@str}, {@str}, [1..10000],
378
[ 1 .. 10000 ], {@str}, {@str}, [ 1 .. 10000 ],
220
379
{@str}, [@rand], {@str}, {@str},