use libdbm.nfs.a if available (libdbm.a is missing dbmclose())
[p5sagit/p5-mst-13.2.git] / lib / Benchmark.pm
CommitLineData
a0d0e21e 1package Benchmark;
2
f06db76b 3=head1 NAME
4
5Benchmark - benchmark running times of code
6
7timethis - run a chunk of code several times
8
9timethese - run several chunks of code several times
10
431d98c2 11cmpthese - print results of timethese as a comparison chart
12
f06db76b 13timeit - run a chunk of code and see how long it goes
14
431d98c2 15countit - see how many times a chunk of code runs in a given time
16
f06db76b 17=head1 SYNOPSIS
18
19 timethis ($count, "code");
20
523cc92b 21 # Use Perl code in strings...
f06db76b 22 timethese($count, {
23 'Name1' => '...code1...',
24 'Name2' => '...code2...',
25 });
26
523cc92b 27 # ... or use subroutine references.
28 timethese($count, {
29 'Name1' => sub { ...code1... },
30 'Name2' => sub { ...code2... },
31 });
32
431d98c2 33 # cmpthese can be used both ways as well
34 cmpthese($count, {
35 'Name1' => '...code1...',
36 'Name2' => '...code2...',
37 });
38
39 cmpthese($count, {
40 'Name1' => sub { ...code1... },
41 'Name2' => sub { ...code2... },
42 });
43
44 # ...or in two stages
45 $results = timethese($count,
46 {
47 'Name1' => sub { ...code1... },
48 'Name2' => sub { ...code2... },
49 },
50 'none'
51 );
52 cmpthese( $results ) ;
53
f06db76b 54 $t = timeit($count, '...other code...')
55 print "$count loops of other code took:",timestr($t),"\n";
56
431d98c2 57 $t = countit($time, '...other code...')
58 $count = $t->iters ;
59 print "$count loops of other code took:",timestr($t),"\n";
60
f06db76b 61=head1 DESCRIPTION
62
63The Benchmark module encapsulates a number of routines to help you
64figure out how long it takes to execute some code.
65
66=head2 Methods
67
68=over 10
69
70=item new
71
72Returns the current time. Example:
73
74 use Benchmark;
75 $t0 = new Benchmark;
76 # ... your code here ...
77 $t1 = new Benchmark;
78 $td = timediff($t1, $t0);
a24a9dfe 79 print "the code took:",timestr($td),"\n";
f06db76b 80
81=item debug
82
83Enables or disable debugging by setting the C<$Benchmark::Debug> flag:
84
523cc92b 85 debug Benchmark 1;
f06db76b 86 $t = timeit(10, ' 5 ** $Global ');
523cc92b 87 debug Benchmark 0;
f06db76b 88
431d98c2 89=item iters
90
91Returns the number of iterations.
92
f06db76b 93=back
94
95=head2 Standard Exports
96
523cc92b 97The following routines will be exported into your namespace
f06db76b 98if you use the Benchmark module:
99
100=over 10
101
102=item timeit(COUNT, CODE)
103
523cc92b 104Arguments: COUNT is the number of times to run the loop, and CODE is
105the code to run. CODE may be either a code reference or a string to
106be eval'd; either way it will be run in the caller's package.
107
108Returns: a Benchmark object.
109
110=item timethis ( COUNT, CODE, [ TITLE, [ STYLE ]] )
111
112Time COUNT iterations of CODE. CODE may be a string to eval or a
113code reference; either way the CODE will run in the caller's package.
114Results will be printed to STDOUT as TITLE followed by the times.
115TITLE defaults to "timethis COUNT" if none is provided. STYLE
116determines the format of the output, as described for timestr() below.
117
6ee623d5 118The COUNT can be zero or negative: this means the I<minimum number of
119CPU seconds> to run. A zero signifies the default of 3 seconds. For
120example to run at least for 10 seconds:
121
122 timethis(-10, $code)
123
124or to run two pieces of code tests for at least 3 seconds:
125
126 timethese(0, { test1 => '...', test2 => '...'})
127
128CPU seconds is, in UNIX terms, the user time plus the system time of
129the process itself, as opposed to the real (wallclock) time and the
130time spent by the child processes. Less than 0.1 seconds is not
131accepted (-0.01 as the count, for example, will cause a fatal runtime
132exception).
133
134Note that the CPU seconds is the B<minimum> time: CPU scheduling and
135other operating system factors may complicate the attempt so that a
136little bit more time is spent. The benchmark output will, however,
137also tell the number of C<$code> runs/second, which should be a more
138interesting number than the actually spent seconds.
139
140Returns a Benchmark object.
141
523cc92b 142=item timethese ( COUNT, CODEHASHREF, [ STYLE ] )
f06db76b 143
523cc92b 144The CODEHASHREF is a reference to a hash containing names as keys
145and either a string to eval or a code reference for each value.
146For each (KEY, VALUE) pair in the CODEHASHREF, this routine will
147call
f06db76b 148
523cc92b 149 timethis(COUNT, VALUE, KEY, STYLE)
f06db76b 150
1d2dff63 151The routines are called in string comparison order of KEY.
152
153The COUNT can be zero or negative, see timethis().
6ee623d5 154
3c6312e9 155Returns a hash of Benchmark objects, keyed by name.
156
523cc92b 157=item timediff ( T1, T2 )
f06db76b 158
523cc92b 159Returns the difference between two Benchmark times as a Benchmark
160object suitable for passing to timestr().
f06db76b 161
6ee623d5 162=item timestr ( TIMEDIFF, [ STYLE, [ FORMAT ] ] )
f06db76b 163
523cc92b 164Returns a string that formats the times in the TIMEDIFF object in
165the requested STYLE. TIMEDIFF is expected to be a Benchmark object
166similar to that returned by timediff().
167
3c6312e9 168STYLE can be any of 'all', 'none', 'noc', 'nop' or 'auto'. 'all' shows
169each of the 5 times available ('wallclock' time, user time, system time,
523cc92b 170user time of children, and system time of children). 'noc' shows all
171except the two children times. 'nop' shows only wallclock and the
172two children times. 'auto' (the default) will act as 'all' unless
173the children times are both zero, in which case it acts as 'noc'.
3c6312e9 174'none' prevents output.
523cc92b 175
176FORMAT is the L<printf(3)>-style format specifier (without the
177leading '%') to use to print the times. It defaults to '5.2f'.
f06db76b 178
179=back
180
181=head2 Optional Exports
182
183The following routines will be exported into your namespace
184if you specifically ask that they be imported:
185
186=over 10
187
523cc92b 188=item clearcache ( COUNT )
189
190Clear the cached time for COUNT rounds of the null loop.
191
192=item clearallcache ( )
f06db76b 193
523cc92b 194Clear all cached times.
f06db76b 195
ac8eabc1 196=item cmpthese ( COUT, CODEHASHREF, [ STYLE ] )
197
198=item cmpthese ( RESULTSHASHREF )
199
200Optionally calls timethese(), then outputs comparison chart. This
277427cf 201chart is sorted from slowest to fastest, and shows the percent
ac8eabc1 202speed difference between each pair of tests. Can also be passed
203the data structure that timethese() returns:
204
205 $results = timethese( .... );
206 cmpthese( $results );
207
277427cf 208Returns the data structure returned by timethese() (or passed in).
ac8eabc1 209
210=item countit(TIME, CODE)
211
212Arguments: TIME is the minimum length of time to run CODE for, and CODE is
213the code to run. CODE may be either a code reference or a string to
214be eval'd; either way it will be run in the caller's package.
215
216TIME is I<not> negative. countit() will run the loop many times to
217calculate the speed of CODE before running it for TIME. The actual
218time run for will usually be greater than TIME due to system clock
219resolution, so it's best to look at the number of iterations divided
220by the times that you are concerned with, not just the iterations.
221
222Returns: a Benchmark object.
223
523cc92b 224=item disablecache ( )
f06db76b 225
523cc92b 226Disable caching of timings for the null loop. This will force Benchmark
227to recalculate these timings for each new piece of code timed.
228
229=item enablecache ( )
230
231Enable caching of timings for the null loop. The time taken for COUNT
232rounds of the null loop will be calculated only once for each
233different COUNT used.
f06db76b 234
ac8eabc1 235=item timesum ( T1, T2 )
236
237Returns the sum of two Benchmark times as a Benchmark object suitable
238for passing to timestr().
239
f06db76b 240=back
241
242=head1 NOTES
243
244The data is stored as a list of values from the time and times
523cc92b 245functions:
f06db76b 246
431d98c2 247 ($real, $user, $system, $children_user, $children_system, $iters)
f06db76b 248
249in seconds for the whole loop (not divided by the number of rounds).
250
251The timing is done using time(3) and times(3).
252
253Code is executed in the caller's package.
254
f06db76b 255The time of the null loop (a loop with the same
256number of rounds but empty loop body) is subtracted
257from the time of the real loop.
258
3c6312e9 259The null loop times can be cached, the key being the
f06db76b 260number of rounds. The caching can be controlled using
261calls like these:
262
523cc92b 263 clearcache($key);
f06db76b 264 clearallcache();
265
523cc92b 266 disablecache();
f06db76b 267 enablecache();
268
3c6312e9 269Caching is off by default, as it can (usually slightly) decrease
270accuracy and does not usually noticably affect runtimes.
271
f06db76b 272=head1 INHERITANCE
273
274Benchmark inherits from no other class, except of course
275for Exporter.
276
277=head1 CAVEATS
278
80eab818 279Comparing eval'd strings with code references will give you
431d98c2 280inaccurate results: a code reference will show a slightly slower
80eab818 281execution time than the equivalent eval'd string.
282
f06db76b 283The real time timing is done using time(2) and
284the granularity is therefore only one second.
285
286Short tests may produce negative figures because perl
523cc92b 287can appear to take longer to execute the empty loop
288than a short test; try:
f06db76b 289
290 timethis(100,'1');
291
292The system time of the null loop might be slightly
293more than the system time of the loop with the actual
a24a9dfe 294code and therefore the difference might end up being E<lt> 0.
f06db76b 295
f06db76b 296=head1 AUTHORS
297
5aabfad6 298Jarkko Hietaniemi <F<jhi@iki.fi>>, Tim Bunce <F<Tim.Bunce@ig.co.uk>>
f06db76b 299
300=head1 MODIFICATION HISTORY
301
302September 8th, 1994; by Tim Bunce.
303
523cc92b 304March 28th, 1997; by Hugo van der Sanden: added support for code
305references and the already documented 'debug' method; revamped
306documentation.
f06db76b 307
6ee623d5 308April 04-07th, 1997: by Jarkko Hietaniemi, added the run-for-some-time
309functionality.
310
3c6312e9 311September, 1999; by Barrie Slaymaker: math fixes and accuracy and
312efficiency tweaks. Added cmpthese(). A result is now returned from
431d98c2 313timethese(). Exposed countit() (was runfor()).
3c6312e9 314
523cc92b 315=cut
a0d0e21e 316
3f943bd9 317# evaluate something in a clean lexical environment
318sub _doeval { eval shift }
319
320#
321# put any lexicals at file scope AFTER here
322#
323
4aa0a1f7 324use Carp;
a0d0e21e 325use Exporter;
326@ISA=(Exporter);
ac8eabc1 327@EXPORT=qw(timeit timethis timethese timediff timestr);
328@EXPORT_OK=qw(timesum cmpthese countit
329 clearcache clearallcache disablecache enablecache);
a0d0e21e 330
331&init;
332
333sub init {
334 $debug = 0;
335 $min_count = 4;
336 $min_cpu = 0.4;
337 $defaultfmt = '5.2f';
338 $defaultstyle = 'auto';
339 # The cache can cause a slight loss of sys time accuracy. If a
340 # user does many tests (>10) with *very* large counts (>10000)
341 # or works on a very slow machine the cache may be useful.
342 &disablecache;
343 &clearallcache;
344}
345
523cc92b 346sub debug { $debug = ($_[1] != 0); }
347
bba8fca5 348# The cache needs two branches: 's' for strings and 'c' for code. The
349# emtpy loop is different in these two cases.
350sub clearcache { delete $cache{"$_[0]c"}; delete $cache{"$_[0]s"}; }
a0d0e21e 351sub clearallcache { %cache = (); }
352sub enablecache { $cache = 1; }
353sub disablecache { $cache = 0; }
354
a0d0e21e 355# --- Functions to process the 'time' data type
356
6ee623d5 357sub new { my @t = (time, times, @_ == 2 ? $_[1] : 0);
358 print "new=@t\n" if $debug;
359 bless \@t; }
a0d0e21e 360
361sub cpu_p { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $pu+$ps ; }
362sub cpu_c { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $cu+$cs ; }
363sub cpu_a { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $pu+$ps+$cu+$cs ; }
364sub real { my($r,$pu,$ps,$cu,$cs) = @{$_[0]}; $r ; }
431d98c2 365sub iters { $_[0]->[5] ; }
a0d0e21e 366
523cc92b 367sub timediff {
a0d0e21e 368 my($a, $b) = @_;
523cc92b 369 my @r;
3f943bd9 370 for (my $i=0; $i < @$a; ++$i) {
a0d0e21e 371 push(@r, $a->[$i] - $b->[$i]);
372 }
373 bless \@r;
374}
375
705cc255 376sub timesum {
377 my($a, $b) = @_;
378 my @r;
379 for (my $i=0; $i < @$a; ++$i) {
380 push(@r, $a->[$i] + $b->[$i]);
381 }
382 bless \@r;
383}
384
523cc92b 385sub timestr {
a0d0e21e 386 my($tr, $style, $f) = @_;
523cc92b 387 my @t = @$tr;
6ee623d5 388 warn "bad time value (@t)" unless @t==6;
389 my($r, $pu, $ps, $cu, $cs, $n) = @t;
a0d0e21e 390 my($pt, $ct, $t) = ($tr->cpu_p, $tr->cpu_c, $tr->cpu_a);
523cc92b 391 $f = $defaultfmt unless defined $f;
a0d0e21e 392 # format a time in the required style, other formats may be added here
80eab818 393 $style ||= $defaultstyle;
523cc92b 394 $style = ($ct>0) ? 'all' : 'noc' if $style eq 'auto';
395 my $s = "@t $style"; # default for unknown style
7be077a2 396 $s=sprintf("%2d wallclock secs (%$f usr %$f sys + %$f cusr %$f csys = %$f CPU)",
523cc92b 397 @t,$t) if $style eq 'all';
7be077a2 398 $s=sprintf("%2d wallclock secs (%$f usr + %$f sys = %$f CPU)",
399 $r,$pu,$ps,$pt) if $style eq 'noc';
400 $s=sprintf("%2d wallclock secs (%$f cusr + %$f csys = %$f CPU)",
401 $r,$cu,$cs,$ct) if $style eq 'nop';
6ee623d5 402 $s .= sprintf(" @ %$f/s (n=$n)", $n / ( $pu + $ps )) if $n;
a0d0e21e 403 $s;
404}
523cc92b 405
406sub timedebug {
a0d0e21e 407 my($msg, $t) = @_;
523cc92b 408 print STDERR "$msg",timestr($t),"\n" if $debug;
a0d0e21e 409}
410
a0d0e21e 411# --- Functions implementing low-level support for timing loops
412
413sub runloop {
414 my($n, $c) = @_;
4aa0a1f7 415
416 $n+=0; # force numeric now, so garbage won't creep into the eval
523cc92b 417 croak "negative loopcount $n" if $n<0;
418 confess "Usage: runloop(number, [string | coderef])" unless defined $c;
a0d0e21e 419 my($t0, $t1, $td); # before, after, difference
420
421 # find package of caller so we can execute code there
523cc92b 422 my($curpack) = caller(0);
423 my($i, $pack)= 0;
a0d0e21e 424 while (($pack) = caller(++$i)) {
425 last if $pack ne $curpack;
426 }
427
3f943bd9 428 my ($subcode, $subref);
429 if (ref $c eq 'CODE') {
430 $subcode = "sub { for (1 .. $n) { local \$_; package $pack; &\$c; } }";
431 $subref = eval $subcode;
432 }
433 else {
434 $subcode = "sub { for (1 .. $n) { local \$_; package $pack; $c;} }";
435 $subref = _doeval($subcode);
436 }
4aa0a1f7 437 croak "runloop unable to compile '$c': $@\ncode: $subcode\n" if $@;
523cc92b 438 print STDERR "runloop $n '$subcode'\n" if $debug;
a0d0e21e 439
3c6312e9 440 # Wait for the user timer to tick. This makes the error range more like
441 # -0.01, +0. If we don't wait, then it's more like -0.01, +0.01. This
442 # may not seem important, but it significantly reduces the chances of
443 # getting a too low initial $n in the initial, 'find the minimum' loop
431d98c2 444 # in &countit. This, in turn, can reduce the number of calls to
bba8fca5 445 # &runloop a lot, and thus reduce additive errors.
446 my $tbase = Benchmark->new(0)->[1];
277427cf 447 while ( ( $t0 = Benchmark->new(0) )->[1] == $tbase ) {} ;
a0d0e21e 448 &$subref;
6ee623d5 449 $t1 = Benchmark->new($n);
a0d0e21e 450 $td = &timediff($t1, $t0);
a0d0e21e 451 timedebug("runloop:",$td);
452 $td;
453}
454
455
456sub timeit {
457 my($n, $code) = @_;
458 my($wn, $wc, $wd);
459
460 printf STDERR "timeit $n $code\n" if $debug;
3c6312e9 461 my $cache_key = $n . ( ref( $code ) ? 'c' : 's' );
bba8fca5 462 if ($cache && exists $cache{$cache_key} ) {
463 $wn = $cache{$cache_key};
523cc92b 464 } else {
bba8fca5 465 $wn = &runloop($n, ref( $code ) ? sub { undef } : '' );
3c6312e9 466 # Can't let our baseline have any iterations, or they get subtracted
467 # out of the result.
468 $wn->[5] = 0;
bba8fca5 469 $cache{$cache_key} = $wn;
a0d0e21e 470 }
471
472 $wc = &runloop($n, $code);
473
474 $wd = timediff($wc, $wn);
a0d0e21e 475 timedebug("timeit: ",$wc);
476 timedebug(" - ",$wn);
477 timedebug(" = ",$wd);
478
479 $wd;
480}
481
6ee623d5 482
483my $default_for = 3;
484my $min_for = 0.1;
485
3c6312e9 486
431d98c2 487sub countit {
488 my ( $tmax, $code ) = @_;
6ee623d5 489
490 if ( not defined $tmax or $tmax == 0 ) {
491 $tmax = $default_for;
492 } elsif ( $tmax < 0 ) {
493 $tmax = -$tmax;
494 }
495
431d98c2 496 die "countit($tmax, ...): timelimit cannot be less than $min_for.\n"
6ee623d5 497 if $tmax < $min_for;
498
3c6312e9 499 my ($n, $tc);
6ee623d5 500
bba8fca5 501 # First find the minimum $n that gives a significant timing.
3c6312e9 502 for ($n = 1; ; $n *= 2 ) {
503 my $td = timeit($n, $code);
504 $tc = $td->[1] + $td->[2];
505 last if $tc > 0.1;
506 }
6ee623d5 507
3c6312e9 508 my $nmin = $n;
509
510 # Get $n high enough that we can guess the final $n with some accuracy.
511 my $tpra = 0.1 * $tmax; # Target/time practice.
512 while ( $tc < $tpra ) {
513 # The 5% fudge is to keep us from iterating again all
514 # that often (this speeds overall responsiveness when $tmax is big
515 # and we guess a little low). This does not noticably affect
516 # accuracy since we're not couting these times.
517 $n = int( $tpra * 1.05 * $n / $tc ); # Linear approximation.
518 my $td = timeit($n, $code);
6ee623d5 519 $tc = $td->[1] + $td->[2];
520 }
521
3c6312e9 522 # Now, do the 'for real' timing(s), repeating until we exceed
523 # the max.
524 my $ntot = 0;
525 my $rtot = 0;
526 my $utot = 0.0;
527 my $stot = 0.0;
528 my $cutot = 0.0;
529 my $cstot = 0.0;
530 my $ttot = 0.0;
531
532 # The 5% fudge is because $n is often a few % low even for routines
533 # with stable times and avoiding extra timeit()s is nice for
534 # accuracy's sake.
535 $n = int( $n * ( 1.05 * $tmax / $tc ) );
536
537 while () {
538 my $td = timeit($n, $code);
539 $ntot += $n;
540 $rtot += $td->[0];
541 $utot += $td->[1];
542 $stot += $td->[2];
6ee623d5 543 $cutot += $td->[3];
544 $cstot += $td->[4];
3c6312e9 545 $ttot = $utot + $stot;
546 last if $ttot >= $tmax;
6ee623d5 547
3c6312e9 548 my $r = $tmax / $ttot - 1; # Linear approximation.
bba8fca5 549 $n = int( $r * $ntot );
6ee623d5 550 $n = $nmin if $n < $nmin;
6ee623d5 551 }
552
553 return bless [ $rtot, $utot, $stot, $cutot, $cstot, $ntot ];
554}
555
a0d0e21e 556# --- Functions implementing high-level time-then-print utilities
557
6ee623d5 558sub n_to_for {
559 my $n = shift;
560 return $n == 0 ? $default_for : $n < 0 ? -$n : undef;
561}
562
a0d0e21e 563sub timethis{
564 my($n, $code, $title, $style) = @_;
6ee623d5 565 my($t, $for, $forn);
566
567 if ( $n > 0 ) {
568 croak "non-integer loopcount $n, stopped" if int($n)<$n;
569 $t = timeit($n, $code);
570 $title = "timethis $n" unless defined $title;
571 } else {
572 $fort = n_to_for( $n );
431d98c2 573 $t = countit( $fort, $code );
6ee623d5 574 $title = "timethis for $fort" unless defined $title;
575 $forn = $t->[-1];
576 }
523cc92b 577 local $| = 1;
523cc92b 578 $style = "" unless defined $style;
3c6312e9 579 printf("%10s: ", $title) unless $style eq 'none';
580 print timestr($t, $style, $defaultfmt),"\n" unless $style eq 'none';
6ee623d5 581
582 $n = $forn if defined $forn;
523cc92b 583
a0d0e21e 584 # A conservative warning to spot very silly tests.
585 # Don't assume that your benchmark is ok simply because
586 # you don't get this warning!
587 print " (warning: too few iterations for a reliable count)\n"
523cc92b 588 if $n < $min_count
a0d0e21e 589 || ($t->real < 1 && $n < 1000)
523cc92b 590 || $t->cpu_a < $min_cpu;
a0d0e21e 591 $t;
592}
593
a0d0e21e 594sub timethese{
595 my($n, $alt, $style) = @_;
596 die "usage: timethese(count, { 'Name1'=>'code1', ... }\n"
597 unless ref $alt eq HASH;
523cc92b 598 my @names = sort keys %$alt;
599 $style = "" unless defined $style;
3c6312e9 600 print "Benchmark: " unless $style eq 'none';
6ee623d5 601 if ( $n > 0 ) {
602 croak "non-integer loopcount $n, stopped" if int($n)<$n;
3c6312e9 603 print "timing $n iterations of" unless $style eq 'none';
6ee623d5 604 } else {
3c6312e9 605 print "running" unless $style eq 'none';
6ee623d5 606 }
3c6312e9 607 print " ", join(', ',@names) unless $style eq 'none';
6ee623d5 608 unless ( $n > 0 ) {
609 my $for = n_to_for( $n );
3c6312e9 610 print ", each for at least $for CPU seconds" unless $style eq 'none';
6ee623d5 611 }
3c6312e9 612 print "...\n" unless $style eq 'none';
523cc92b 613
614 # we could save the results in an array and produce a summary here
a0d0e21e 615 # sum, min, max, avg etc etc
3c6312e9 616 my %results;
4dbb2df9 617 foreach my $name (@names) {
3c6312e9 618 $results{$name} = timethis ($n, $alt -> {$name}, $name, $style);
4dbb2df9 619 }
3c6312e9 620
621 return \%results;
a0d0e21e 622}
623
3c6312e9 624sub cmpthese{
625 my $results = ref $_[0] ? $_[0] : timethese( @_ );
626
627 return $results
628 if defined $_[2] && $_[2] eq 'none';
629
630 # Flatten in to an array of arrays with the name as the first field
631 my @vals = map{ [ $_, @{$results->{$_}} ] } keys %$results;
632
633 for (@vals) {
634 # The epsilon fudge here is to prevent div by 0. Since clock
635 # resolutions are much larger, it's below the noise floor.
636 my $rate = $_->[6] / ( $_->[2] + $_->[3] + 0.000000000000001 );
637 $_->[7] = $rate;
638 }
639
640 # Sort by rate
641 @vals = sort { $a->[7] <=> $b->[7] } @vals;
642
643 # If more than half of the rates are greater than one...
644 my $display_as_rate = $vals[$#vals>>1]->[7] > 1;
645
646 my @rows;
647 my @col_widths;
648
649 my @top_row = (
650 '',
651 $display_as_rate ? 'Rate' : 's/iter',
652 map { $_->[0] } @vals
653 );
654
655 push @rows, \@top_row;
656 @col_widths = map { length( $_ ) } @top_row;
657
658 # Build the data rows
659 # We leave the last column in even though it never has any data. Perhaps
660 # it should go away. Also, perhaps a style for a single column of
661 # percentages might be nice.
662 for my $row_val ( @vals ) {
663 my @row;
664
665 # Column 0 = test name
666 push @row, $row_val->[0];
667 $col_widths[0] = length( $row_val->[0] )
668 if length( $row_val->[0] ) > $col_widths[0];
669
670 # Column 1 = performance
671 my $row_rate = $row_val->[7];
672
673 # We assume that we'll never get a 0 rate.
674 my $a = $display_as_rate ? $row_rate : 1 / $row_rate;
675
676 # Only give a few decimal places before switching to sci. notation,
677 # since the results aren't usually that accurate anyway.
678 my $format =
679 $a >= 100 ?
680 "%0.0f" :
681 $a >= 10 ?
682 "%0.1f" :
683 $a >= 1 ?
684 "%0.2f" :
685 $a >= 0.1 ?
686 "%0.3f" :
687 "%0.2e";
688
689 $format .= "/s"
690 if $display_as_rate;
691 # Using $b here due to optimizing bug in _58 through _61
692 my $b = sprintf( $format, $a );
693 push @row, $b;
694 $col_widths[1] = length( $b )
695 if length( $b ) > $col_widths[1];
696
697 # Columns 2..N = performance ratios
698 my $skip_rest = 0;
699 for ( my $col_num = 0 ; $col_num < @vals ; ++$col_num ) {
700 my $col_val = $vals[$col_num];
701 my $out;
702 if ( $skip_rest ) {
703 $out = '';
704 }
705 elsif ( $col_val->[0] eq $row_val->[0] ) {
706 $out = "--";
707 # $skip_rest = 1;
708 }
709 else {
710 my $col_rate = $col_val->[7];
711 $out = sprintf( "%.0f%%", 100*$row_rate/$col_rate - 100 );
712 }
713 push @row, $out;
714 $col_widths[$col_num+2] = length( $out )
715 if length( $out ) > $col_widths[$col_num+2];
716
717 # A little wierdness to set the first column width properly
718 $col_widths[$col_num+2] = length( $col_val->[0] )
719 if length( $col_val->[0] ) > $col_widths[$col_num+2];
720 }
721 push @rows, \@row;
722 }
723
724 # Equalize column widths in the chart as much as possible without
725 # exceeding 80 characters. This does not use or affect cols 0 or 1.
726 my @sorted_width_refs =
727 sort { $$a <=> $$b } map { \$_ } @col_widths[2..$#col_widths];
728 my $max_width = ${$sorted_width_refs[-1]};
729
277427cf 730 my $total = @col_widths - 1 ;
3c6312e9 731 for ( @col_widths ) { $total += $_ }
732
733 STRETCHER:
734 while ( $total < 80 ) {
735 my $min_width = ${$sorted_width_refs[0]};
736 last
737 if $min_width == $max_width;
738 for ( @sorted_width_refs ) {
739 last
740 if $$_ > $min_width;
741 ++$$_;
742 ++$total;
743 last STRETCHER
744 if $total >= 80;
745 }
746 }
747
748 # Dump the output
749 my $format = join( ' ', map { "%${_}s" } @col_widths ) . "\n";
750 substr( $format, 1, 0 ) = '-';
751 for ( @rows ) {
752 printf $format, @$_;
753 }
754
755 return $results;
756}
757
758
a0d0e21e 7591;