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