File Coverage

blib/lib/Benchmark/Serialize.pm
Criterion Covered Total %
statement 18 134 13.4
branch 0 48 0.0
condition 0 3 0.0
subroutine 6 20 30.0
pod 1 11 9.0
total 25 216 11.5


line stmt bran cond sub pod time code
1             package Benchmark::Serialize;
2              
3 1     1   19976 use strict;
  1         2  
  1         40  
4 1     1   6 use warnings;
  1         2  
  1         54  
5              
6             =head1 NAME
7              
8             Benchmark::Serialize - Benchmarks of serialization modules
9              
10             =head1 VERSION
11              
12             Version 0.08
13              
14             =cut
15              
16             our $VERSION = '0.08';
17              
18             =head1 SYNOPSIS
19              
20             use Benchmark::Serialize qw(cmpthese);
21              
22             my $structure = {
23             array => [ 'a' .. 'j' ],
24             hash => { 'a' .. 'z' },
25             string => 'x' x 200,
26             };
27              
28             cmpthese( -5, $structure, qw(:core :json :yaml) );
29              
30             =head1 DESCRIPTION
31              
32             This module encapsulates some basic benchmarks to help you choose a module
33             for serializing data. Note that using this module is only a part of chosing a
34             serialization format. Other factors than the benchmarked might be of
35             relevance!
36              
37             Included is support for 24 different serialization modules. Also supported
38             is the Data::Serializer wrapper providing a unified interface for
39             serialization and some extra features. Benchmarking of specialized modules
40             made with Protocol Buffers for Perl/XS (protobuf-perlxs) is also available.
41              
42             =head2 Functions
43              
44             This module provides the following functions
45              
46             =over 5
47              
48             =item cmpthese(COUNT, STRUCTURE, BENCHMARKS ...)
49              
50             Benchmark COUNT interations of a list of modules. A benchmark is either a name
51             of a supported module, a tag, or a hash ref containing at least an inflate, a
52             deflate, and a name attribute:
53              
54             {
55             name => 'JSON::XS',
56             deflate => sub { JSON::XS::encode_json($_[0]) }
57             inflate => inflate => sub { JSON::XS::decode_json($_[0]) }
58             }
59              
60             By default Benchmark::Serialize will try to use the name attribute as a module
61             to be loaded. This can be overridden by having a packages attribute with an
62             arrayref containing modules to be loaded.
63              
64             =back
65              
66             =head2 Benchmark tags
67              
68             The following tags are supported
69              
70             =over 5
71              
72             =item :all - All modules with premade benchmarks
73              
74             =item :default - A default set of serialization modules
75              
76             =item :core - Serialization modules included in core
77              
78             =item :json - JSON modules
79              
80             =item :yaml - YAML modules
81              
82             =item :xml - XML formats
83              
84             =back
85              
86             =cut
87              
88 1     1   811 use Benchmark qw[timestr];
  1         7107  
  1         5  
89 1     1   865 use Test::Deep::NoTest;
  1         18763  
  1         6  
90              
91 1     1   868 use Benchmark::Serialize::Library;
  1         2  
  1         13  
92              
93 1     1   44 use Exporter qw(import);
  1         2  
  1         1883  
94             our @EXPORT_OK = qw( cmpthese );
95             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
96              
97             our $benchmark_deflate = 1; # boolean
98             our $benchmark_inflate = 1; # boolean
99             our $benchmark_roundtrip= 1; # boolean
100             our $benchmark_size = 1; # boolean
101             our $verbose = 0; # boolean
102             our $output = 'chart'; # chart or list
103              
104             sub cmpthese {
105 0     0 1   my $iterations = shift;
106 0           my $structure = shift;
107 0           my @benchmarks = Benchmark::Serialize::Library->load( @_ );
108              
109 0           my $width = width(map { $_->name } @benchmarks);
  0            
110 0           my $results = { };
111              
112 0           print "\nModules\n";
113              
114 0           BENCHMARK:
115              
116 0           foreach my $benchmark ( sort { $a->name cmp $b->name } @benchmarks ) {
117 0           my $name = $benchmark->name;
118              
119 0           my ($deflated, $inflated);
120             eval {
121 0           $deflated = $benchmark->deflate($structure);
122 0           $inflated = $benchmark->inflate($deflated);
123 0           1;
124 0 0         } or do {
125 0           warn "Benchmark $name died with:\n $@\n";
126 0           next BENCHMARK;
127             };
128            
129 0           my ($likeliness, $diag) = likeliness( $inflated, $structure );
130 0           printf( "%-${width}s : %8s %s\n", $benchmark->name, $benchmark->version, $likeliness);
131              
132 0 0 0       print Test::Deep::deep_diag($diag), "\n" if defined($diag) and $Benchmark::Serialize::verbose;
133              
134 0 0         $results->{deflate}->{$name} = timeit_deflate( $iterations, $structure, $benchmark )
135             if $benchmark_deflate;
136              
137 0 0         $results->{inflate}->{$name} = timeit_inflate( $iterations, $structure, $benchmark )
138             if $benchmark_inflate;
139              
140 0 0         $results->{roundtrip}->{$name} = timeit_roundtrip( $iterations, $structure, $benchmark )
141             if $benchmark_roundtrip;
142              
143 0           $results->{size}->{$name} = length( $deflated );
144             }
145              
146 0 0         output( 'Sizes', "size", $output, $results->{size}, $width )
147             if $benchmark_size;
148              
149 0 0         output( 'Deflate (perl -> serialized)', "time", $output, $results->{deflate}, $width )
150             if $benchmark_deflate;
151              
152 0 0         output( 'Inflate (serialized -> perl)', "time", $output, $results->{inflate}, $width )
153             if $benchmark_inflate;
154              
155 0 0         output( 'Roundtrip', "time", $output, $results->{roundtrip}, $width )
156             if $benchmark_roundtrip;
157             }
158              
159             sub output {
160 0     0 0   my $title = shift;
161 0           my $type = shift;
162 0           my $output = shift;
163 0           printf( "\n%s\n", $title );
164 0 0         if ( $type eq "size" ) {
    0          
165 0 0         ($output eq "list") ? &size_list : &size_chart ;
166             } elsif ( $type eq "time" ) {
167 0 0         ($output eq "list") ? &time_list : &time_chart ;
168              
169             } else {
170 0           warn("Unknown data type: $type");
171             }
172             }
173              
174             sub time_chart {
175 0     0 0   my $results = shift;
176 0           Benchmark::cmpthese($results);
177             }
178              
179             sub time_list {
180 0     0 0   my $results = shift;
181 0           my $width = shift;
182 0           foreach my $title ( sort keys %{ $results } ) {
  0            
183 0           printf( "%-${width}s %s\n", $title, timestr( $results->{ $title } ) );
184             }
185             }
186              
187             sub size_chart {
188 0     0 0   my $results = shift;
189 0           my @vals = sort { $a->[1] <=> $b->[1] } map { [ $_, $results->{$_} ] } keys %$results;
  0            
  0            
190              
191 0           my @rows = ( [
192             '',
193             'bytes',
194 0           map { $_->[0] } @vals,
195             ] );
196              
197 0           my @col_width = map { length ( $_ ) } @{ $rows[0] };
  0            
  0            
198              
199 0           for my $row_val ( @vals ) {
200 0           my @row;
201              
202 0           push @row, $row_val->[0], $row_val->[1];
203 0 0         $col_width[0] = ( length ( $row_val->[0] ) > $col_width[0] ? length( $row_val->[0] ) : $col_width[0] );
204 0 0         $col_width[1] = ( length ( $row_val->[1] ) > $col_width[1] ? length( $row_val->[1] ) : $col_width[1] );
205              
206             # Columns 2..N = performance ratios
207 0           for my $col_num ( 0 .. $#vals ) {
208 0           my $col_val = $vals[$col_num];
209 0           my $out;
210              
211 0 0         if ( $col_val->[0] eq $row_val->[0] ) {
212 0           $out = "--";
213             } else {
214 0           $out = sprintf( "%.0f%%", 100*$row_val->[1]/$col_val->[1] - 100 );
215             }
216              
217 0           push @row, $out;
218 0 0         $col_width[$col_num+2] = ( length ( $out ) > $col_width[$col_num+2] ? length ( $out ) : $col_width[$col_num+2]);
219             }
220 0           push @rows, \@row;
221             }
222              
223             # Pasted from Benchmark.pm
224             # Equalize column widths in the chart as much as possible without
225             # exceeding 80 characters. This does not use or affect cols 0 or 1.
226 0           my @sorted_width_refs =
227 0           sort { $$a <=> $$b } map { \$_ } @col_width[2..$#col_width];
  0            
228 0           my $max_width = ${$sorted_width_refs[-1]};
  0            
229              
230 0           my $total = @col_width - 1 ;
231 0           for ( @col_width ) { $total += $_ }
  0            
232              
233             STRETCHER:
234 0           while ( $total < 80 ) {
235 0           my $min_width = ${$sorted_width_refs[0]};
  0            
236             last
237 0 0         if $min_width == $max_width;
238 0           for ( @sorted_width_refs ) {
239             last
240 0 0         if $$_ > $min_width;
241 0           ++$$_;
242 0           ++$total;
243             last STRETCHER
244 0 0         if $total >= 80;
245             }
246             }
247              
248             # Dump the output
249 0           my $format = join( ' ', map { "%${_}s" } @col_width ) . "\n";
  0            
250 0           substr( $format, 1, 0 ) = '-';
251 0           for ( @rows ) {
252 0           printf $format, @$_;
253             }
254             }
255              
256             sub size_list {
257 0     0 0   my $results = shift;
258 0           my $width = shift;
259 0           foreach my $title ( sort keys %{ $results } ) {
  0            
260 0           printf( "%-${width}s : %d bytes\n", $title, $results->{ $title } );
261             }
262             }
263              
264             sub timeit_deflate {
265 0     0 0   my ( $iterations, $structure, $benchmark ) = @_;
266 0     0     return Benchmark::timethis( $iterations, sub { $benchmark->deflate($structure) }, '', 'none' );
  0            
267             }
268              
269             sub timeit_inflate {
270 0     0 0   my ( $iterations, $structure, $benchmark ) = @_;
271 0           my $deflated = $benchmark->deflate($structure);
272 0     0     return Benchmark::timethis( $iterations, sub { $benchmark->inflate($deflated) }, '', 'none' );
  0            
273             }
274              
275             sub timeit_roundtrip {
276 0     0 0   my ( $iterations, $structure, $benchmark ) = @_;
277 0     0     return Benchmark::timethis( $iterations, sub { $benchmark->inflate( $benchmark->deflate( $structure )) }, '', 'none' );
  0            
278             }
279              
280             sub width {
281 0     0 0   return length( ( sort { length $a <=> length $b } @_ )[-1] );
  0            
282             }
283              
284             sub likeliness {
285 0     0 0   my ($got, $expected) = @_;
286 0           my ($ok, $diag);
287              
288 0           ($ok, $diag) = Test::Deep::cmp_details( $got, $expected );
289 0 0         return ("Identical", undef) if $ok;
290              
291 0           ($ok, $diag) = Test::Deep::cmp_details( $got, noclass($expected) );
292 0 0         return ("Changes blessing", undef) if $ok;
293              
294 0           ($ok, $diag) = Test::Deep::cmp_details( $got, noclass(superhashof $expected) );
295 0 0         return ("Adds content", undef) if $ok;
296              
297 0           ($ok, $diag) = Test::Deep::cmp_details( $got, noclass(subhashof $expected) );
298 0 0         return ("Removes content", undef) if $ok;
299              
300 0           return ("Changes content", $diag);
301             }
302              
303             =head1 RESULTS
304              
305             See the README file for example results.
306              
307             =head1 AUTHOR
308              
309             Peter Makholm, C<< >>
310              
311             =head1 BUGS
312              
313             Please report any bugs or feature requests to C
314             rt.cpan.org>, or through
315             the web interface at
316             L. I will
317             be notified, and then you'll
318             automatically be notified of progress on your bug as I make changes.
319              
320             =head1 ACKNOWLEDGEMENTS
321              
322             This module started out as a script written by Christian Hansen, see
323             http://idisk.mac.com/christian.hansen/Public/perl/serialize.pl
324              
325             =head1 COPYRIGHT & LICENSE
326              
327             Copyright 2009 Peter Makholm.
328              
329             This program is free software; you can redistribute it and/or modify it
330             under the terms of either: the GNU General Public License as published
331             by the Free Software Foundation; or the Artistic License.
332              
333             See http://dev.perl.org/licenses/ for more information.
334              
335              
336             =cut
337              
338             1;