File Coverage

blib/lib/Benchmark/Dumb.pm
Criterion Covered Total %
statement 116 164 70.7
branch 21 42 50.0
condition 7 21 33.3
subroutine 18 20 90.0
pod 8 8 100.0
total 170 255 66.6


line stmt bran cond sub pod time code
1             package Benchmark::Dumb;
2 2     2   14742 use strict;
  2         5  
  2         62  
3 2     2   12 use warnings;
  2         4  
  2         53  
4 2     2   470 use Dumbbench;
  2         4  
  2         33  
5 2     2   9 use Carp ();
  2         3  
  2         1985  
6              
7             our @CARP_NOT = qw(
8             Dumbbench
9             Dumbbench::Instance
10             Dumbbench::Instance::Cmd
11             Dumbbench::Instance::PerlEval
12             Dumbbench::Instance::PerlSub
13             Dumbbench::Result
14             );
15              
16             our $VERSION = '0.111';
17              
18             require Exporter;
19              
20             our @ISA = qw(Exporter);
21             our @EXPORT = ();
22             our @EXPORT_OK = qw(
23             timeit timethis timethese cmpthese
24             timediff timestr timesum
25             );
26             our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
27              
28             # strip out :hireswallclock
29             sub import {
30 2     2   16 my $class = shift;
31 2         6 my @args = grep $_ ne ':hireswallclock', @_;
32 2         200 $class->export_to_level(1, $class, @args);
33             }
34              
35             sub _dumbbench_from_count {
36 3     3   5 my $count = shift;
37 3         7 my %opt = @_;
38 3 50       8 if ($count < 0) {
39 0         0 Carp::croak("The negative-value variant of COUNT in benchmarks is not supported by Benchmark::Dumb");
40             }
41 3 50       9 if ($count >= 1) {
42 3         7 $opt{initial_runs} = int($count);
43             }
44 3 50       7 if (int($count) != $count) {
45 0         0 $opt{target_rel_precision} = $count - int($count);
46             }
47              
48 3         17 return Dumbbench->new(
49             # TODO configurable default settings?
50             %opt,
51             );
52             }
53              
54             sub _prepare {
55 4     4   7 my $count = shift;
56 4         6 my $code = shift;
57 4         8 my $name = shift;
58 4   66     14 my $bench = shift || _dumbbench_from_count($count); # FIXME %opt?
59 4 100       16 $name = 'anon' if not defined $name;
60 4 100       11 my $class = ref($code) ? "Dumbbench::Instance::PerlSub" : "Dumbbench::Instance::PerlEval";
61 4         39 $bench->add_instances(
62             $class->new(
63             name => $name, code => $code,
64             )
65             );
66 4         10 return $bench;
67             }
68              
69             sub timeit {
70 2     2 1 1162 my $count = shift;
71 2         4 my $code = shift;
72 2         6 my $bench = _prepare($count, $code);
73 2         6 $bench->run;
74              
75 2         8 return __PACKAGE__->_new(
76             instance => ($bench->instances)[0],
77             );
78             }
79              
80             sub timethis {
81 1     1 1 2868 my $count = shift;
82 1         3 my $code = shift;
83 1         2 my $title = shift;
84 1 50       4 $title = 'timethis ' . $count if not defined $title;
85 1         2 my $style = shift;
86 1         4 my $res = timeit($count, $code);
87 1         5 $res->{name} = $title;
88 1         5 print "$title: ", $res->timestr($style), "\n";
89 1         22 return $res;
90             }
91              
92             sub _timethese_guts {
93 1     1   2 my $count = shift;
94 1         2 my $instances = shift;
95 1         1 my $silent = shift;
96              
97 1         2 my $max_name_len = 1;
98 1         3 my $bench = _dumbbench_from_count($count); # FIXME %opt?
99 1         7 foreach my $name (sort keys %$instances) {
100 2         7 _prepare($count, $instances->{$name}, $name, $bench);
101 2 100       7 $max_name_len = length($name) if length($name) > $max_name_len;
102             }
103              
104 1         4 $bench->run;
105 1 50       4 $bench->verbosity(0) if $silent;
106              
107 1 50       4 if (not $silent) {
108 1         4 print "Benchmark: ran ",
109             join(', ', map $_->name, $bench->instances),
110             ".\n";
111             }
112              
113 1         5 my $result = {};
114 1         4 foreach my $inst ($bench->instances) {
115 2         9 my $r = $result->{$inst->name} = __PACKAGE__->_new(
116             instance => $inst,
117             );
118 2 50       6 if (not $silent) {
119 2         24 printf("%${max_name_len}s: ", $r->name);
120 2         9 print $r->timestr(), "\n";
121             }
122             }
123 1         16 return $result;
124             }
125              
126             sub timethese {
127 1     1 1 2144 my $count = shift;
128 1         3 my $instances = shift;
129 1 50 33     12 Carp::croak("Need count and code-hashref as arguments")
      33        
130             if not defined $count or not ref($instances) or not ref($instances) eq 'HASH';
131              
132 1         5 return _timethese_guts($count, $instances, 0);
133             }
134              
135              
136             sub cmpthese {
137 0     0 1 0 my $count = shift;
138 0         0 my $codehashref = shift;
139 0   0     0 my $style = shift || ''; # ignored unless 'none'
140              
141 0         0 my $results;
142 0 0       0 if (ref($count)) {
143 0         0 $results = $count;
144             }
145             else {
146 0         0 $results = _timethese_guts($count, $codehashref, 'silent');
147             }
148              
149 0         0 my @sort_res = map [$_, $results->{$_}, $results->{$_}->_rate], keys %$results;
150 0         0 @sort_res = sort { $a->[2] <=> $b->[2] } @sort_res;
  0         0  
151              
152 0         0 my @cols = map $_->[0], @sort_res;
153 0         0 my @rows = (
154             ['', 'Rate', @cols]
155             );
156              
157 0         0 foreach my $record (@sort_res) {
158 0         0 my ($name, $bench, $rate) = @$record;
159 0         0 my $rstr = $bench->_rate_str($rate) . '/s';
160 0         0 $rstr =~ s/\s+//g;
161 0         0 my @row;
162 0         0 push @row, $name, $rstr;
163              
164 0         0 foreach my $cmp_record (@sort_res) {
165 0         0 my ($cmp_name, $cmp_bench, $cmp_rate) = @$cmp_record;
166 0 0       0 if ($cmp_name eq $name) {
167 0         0 push @row, '--';
168 0         0 next;
169             }
170              
171 0         0 my $cmp = 100*$rate/$cmp_rate - 100;
172             # skip the uncertainty if it's less than one permille
173             # absolute or relative
174 0 0 0     0 if ($cmp->raw_error->[0] < 1.e-1
175             or ($cmp->raw_error->[0]+1.e-15)/$cmp->raw_number < 1.e-3)
176             {
177 0         0 my $rounded = Number::WithError::round_a_number($cmp->raw_number, -1);
178 0         0 push @row, sprintf('%.1f', $rounded) . '%';
179             }
180             else {
181 0         0 my $cmp_str = $bench->_rate_str($cmp).'%'; # abuse
182 0         0 $cmp_str =~ s/\s+//g;
183 0         0 push @row, $cmp_str;
184             }
185             }
186              
187 0         0 push @rows, \@row;
188             }
189              
190 0 0       0 if (lc($style) ne 'none') {
191             # find the max column lengths
192             # could be done in the above iteration, too
193 0         0 my $ncols = @{$rows[0]};
  0         0  
194 0         0 my @col_len = ((0) x $ncols);
195 0         0 foreach my $row (@rows) {
196 0         0 foreach my $colno (0..$ncols-1) {
197 0 0       0 $col_len[$colno] = length($row->[$colno])
198             if length($row->[$colno]) > $col_len[$colno];
199             }
200             }
201              
202 0         0 my $format = join( ' ', map { "%${_}s" } @col_len) . "\n";
  0         0  
203 0         0 substr( $format, 1, 0 ) = '-'; # right-align name
204              
205 0         0 foreach my $row (@rows) {
206 0         0 printf($format, @$row);
207             }
208             }
209              
210 0         0 return \@rows;
211             }
212              
213              
214             #####################################
215             # the fake-OO stuff
216             use Class::XSAccessor {
217 2         14 getters => {
218             _result => 'result',
219             name => 'name',
220             },
221 2     2   15 };
  2         3  
222             # No. Users aren't meant to create new objects at this point.
223             sub _new {
224 8     8   13 my $class = shift;
225 8 100       22 $class = ref($class) if ref($class);
226 8         19 my %args = @_;
227 8         17 my $self = bless {} => $class;
228 8 100       18 if (defined $args{instance}) {
229 4         9 my $inst = $args{instance};
230 4         13 $self->{name} = $inst->name;
231 4         19 $self->{result} = $inst->result->new;
232             }
233             else {
234 4         12 %$self = %args;
235             }
236 8         36 return $self;
237             }
238              
239             sub iters {
240 0     0 1 0 my $self = shift;
241 0         0 return $self->_result->nsamples;
242             }
243              
244             sub timesum {
245 1     1 1 2043 my $self = shift;
246 1         2 my $other = shift;
247 1         8 my $result = $self->_result + $other->_result;
248 1         96 return $self->_new(result => $result, name => '');
249             }
250              
251              
252             sub timediff {
253 1     1 1 5 my $self = shift;
254 1         2 my $other = shift;
255 1         5 my $result = $self->_result - $other->_result;
256 1         105 return $self->_new(result => $result, name => '');
257             }
258              
259             sub timestr {
260 4     4 1 3202 my $self = shift;
261 4   50     16 my $style = shift || '';
262 4   50     16 my $format = shift || '5.2f';
263              
264 4         8 $style = lc($style);
265 4 50       11 return("") if $style eq 'none'; # what's the point?
266              
267 4         6 my $res = $self->_result;
268 4         17 my $time = $res->number;
269 4         246 my $err = $res->error->[0];
270 4 50       246 my $rel = ($time > 0 ? $err/$time : 1) * 100;
271 4         7 my $digits;
272 4 50       24 if ($rel =~ /^([0\.]*)/) { # quick'n'dirty significant digits
273 4         9 $digits = length($1) + 1;
274             }
275 4         17 $rel = sprintf("\%.${digits}f", $rel);
276              
277 4         9 my $rate = $self->_rate_str;
278 4         18 my $str = "$time +- $err wallclock secs ($rel%) @ ($rate)/s (n=" . $res->nsamples . ")";
279              
280 4         60 return $str;
281             }
282              
283             sub _rate_str {
284 4     4   8 my $self = shift;
285 4   33     12 my $per_sec = shift || $self->_rate;
286              
287             # The joys of people-not-enjoying-scientific-notation
288 4         248 my $digit = $per_sec->significant_digit;
289 4         83 my $before_radix = length(int($per_sec->raw_number));
290             # FIXME: not clear if this makes sense. Need to revisit later in a day.
291             #$before_radix = 0 if int($per_sec->raw_number) == 0;
292 4         21 $digit = $before_radix - $digit;
293 4         9 my $ps_format = "%${digit}g";
294 4         12 my $ps_string = sprintf("$ps_format +- $ps_format", $per_sec->number*1., $per_sec->error->[0]);
295 4         438 return $ps_string;
296             }
297              
298             sub _rate {
299 4     4   7 my $self = shift;
300 4         8 my $res = $self->_result;
301 4         14 my $per_sec = 1./($res+1.e-20); # the joys of overloading. See Number::WithError.
302 4         149 return $per_sec;
303             }
304              
305              
306              
307             1;
308              
309             __END__