source: trunk/third/perl/lib/Benchmark.pm @ 18450

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