File Coverage

blib/lib/Benchmark/Dumb.pm
Criterion Covered Total %
statement 116 165 70.3
branch 21 42 50.0
condition 7 21 33.3
subroutine 18 20 90.0
pod 8 8 100.0
total 170 256 66.4


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