File Coverage

blib/lib/Benchmark/MCE.pm
Criterion Covered Total %
statement 274 274 100.0
branch 158 168 94.0
condition 72 89 80.9
subroutine 28 28 100.0
pod 4 4 100.0
total 536 563 95.2


line stmt bran cond sub pod time code
1             package Benchmark::MCE;
2              
3 21     21   6015146 use strict;
  21         44  
  21         872  
4 21     21   237 use warnings;
  21         80  
  21         1119  
5              
6 21     21   143 use Config;
  21         42  
  21         955  
7 21     21   105 use Exporter 'import';
  21         89  
  21         815  
8 21     21   107 use List::Util qw(min max sum);
  21         41  
  21         2045  
9 21     21   145 use Time::HiRes qw(CLOCK_MONOTONIC);
  21         41  
  21         191  
10              
11 21     21   15837 use MCE::Loop;
  21         1746176  
  21         185  
12 21     21   24106 use System::CPU;
  21         89917  
  21         1153  
13 21     21   12794 use System::Info;
  21         786907  
  21         117821  
14              
15             our $VERSION = '1.03';
16             our @EXPORT = qw(system_identity suite_run calc_scalability suite_calc);
17             our $MONO_CLOCK = $^O !~ /win/i || $Time::HiRes::VERSION >= 1.9764;
18             our $QUIET = 0;
19              
20             =head1 NAME
21              
22             Benchmark::MCE - Perl multi-core benchmarking framework
23              
24             =head1 SYNOPSIS
25              
26             use Benchmark::MCE;
27              
28             # Run 2 benchmarks (custom functions) and time them on a single core:
29             my %stat_single = suite_run({
30             threads => 1,
31             bench => {
32             Bench1 => sub { ...code1... },
33             Bench2 => '...code2...' # String is also fine
34             }
35             );
36              
37             # Run each across multiple cores.
38             # Use the extended (arrayref) definition to check for correctness of output.
39             my %stat_multi = suite_run({
40             threads => system_identity(1), # Workers count equal to system logical cores
41             bench => {
42             Bench1 => [\&code1, $expected_output1],
43             Bench2 => [\&code2, $expected_output2],
44             }
45             );
46            
47             # Calculate the multi/single core scalability
48             my %scal = calc_scalability(\%stat_single, \%stat_multi);
49              
50             =head1 DESCRIPTION
51              
52             A benchmarking framework originally designed for the L multi-core
53             CPU benchmarking suite. Released as a stand-alone to be used for custom benchmarks
54             of any type, as well as other kinds of stress-testing, throughput testing etc.
55              
56             You define custom functions (usually with randomized workloads) that can be run on
57             any number of parallel workers, using the low-overhead Many-Core Engine (L).
58              
59             =head1 FUNCTIONS
60            
61             =head2 C
62              
63             my $cores = system_identity($quiet?);
64              
65             Prints out software/hardware configuration and returns the number of logical cores
66             detected using L.
67              
68             Any argument will suppress printout and will only return the number of cores.
69              
70             =head2 C
71              
72             my %stats = suite_run(\%options);
73              
74             Runs the benchmark suite given the C<%options> and prints results. Returns a hash
75             with run stats that looks like this:
76              
77             %stats = (
78             $bench_name_1 => {times => [ ... ], scores => [ ... ]},
79             ...
80             _total => {times => [ ... ], scores => [ ... ]},
81             _opt => {iter => $iterations, threads => $no_threads, ...}
82             );
83              
84             Note that the times reported will be average times per thread (or per function
85             call if you prefer), however the scores reported (if a reference time is supplied)
86             are sums across all threads. So you expect for ideal scaling 1 thread vs 2 threads
87             to return the same times, double the scores.
88              
89             =head3 Options:
90              
91             =over 4
92              
93             =item * C (HashRef, with alias C) B:
94             A hashref with keys being your unique custom benchmark names and values being
95             arrays:
96              
97             C<< name => [ $coderef, $expected?, $ref_time?, $quick_arg?, $normal_arg? ] >>
98              
99             where:
100              
101             =over 4
102              
103             =item * C<$coderef> B:
104             Reference to your benchmark function. See L for more details.
105              
106             =item * C<$expected>:
107             Expected output of the benchmark function on successful run (for PASS/FAIL - PASS
108             will be always assumed is parameter is undefined).
109              
110             =item * C<$ref_time>:
111             Reference time in seconds for score of 1000.
112              
113             =item * C<$quick_arg>:
114             Argument to pass to the benchmark function in C mode (for workload scaling).
115              
116             =item * C<$normal_arg>:
117             Argument to pass to the benchmark function in normal mode (for workload scaling).
118              
119             =back
120              
121             =item * C (Int; default 1):
122             Parallel benchmark threads. They are L workers, so not 'threads' in the technical
123             sense. Each of the benchmarks defined will launch on each of the threads, hence the
124             total workload is multiplied by the number of C. Times will be averaged
125             across threads, while scores will be summed.
126              
127             =item * C (Int; default 1):
128             Number of suite iterations (with min/max/avg at the end when > 1).
129              
130             =item * C (Regex):
131             Only run benchmarks whose names match regex.
132              
133             =item * C (Regex):
134             Skip benchmarks whose names match regex.
135              
136             =item * C (CodeRef):
137             Custom filter callback for finer control. It receives C<($opt, $bench, $bench_def)>
138             and should return true to run a benchmark.
139              
140             =item * C
141             Report time (sec) instead of score. Set to true by C or if at least one
142             benchmark has no reference time declared. Otherwise score output is the default.
143              
144             =item * C (Bool; default 0):
145             Use each benchmark's quick argument and imply C.
146              
147             =item * C (Int; default 1):
148             Scale the bench workload (number of calls of the benchmark functions) by x times.
149             Forced to 1 with C or C.
150              
151             =item * C (Bool; default 0):
152             Show relative standard deviation (for C > 1).
153              
154             =item * C (Int; default 0):
155             Number of seconds to sleep after each benchmark run.
156              
157             =item * C (Int, seconds):
158             Minimum duration in seconds for suite run (overrides C).
159              
160             =item * C (Int; default 1):
161             Define a fixed seed to keep runs reproducible when your benchmark functions use
162             C. The seed will be passed to C before each call to a benchmark
163             function. Set to 0 to skip rand seeding.
164              
165             =item * C (Bool; default 0):
166             Do not check for Pass/Fail even if reference output is defined.
167              
168             =item * C (Bool; default 0):
169             Do not run under L (sets C, C).
170              
171             =back
172              
173             =head2 C
174              
175             my %scal = calc_scalability(\%stat_single, \%stat_multi, $keep_outliers?);
176              
177             Given the C<%stat_single> results of a single-threaded C and C<%stat_multi>
178             results of a multi-threaded run, will calculate, print and return the multi-thread
179             scalability (including averages, ranges etc for multiple iterations).
180              
181             Unless C<$keep_outliers> is true, the overall scalability is an average after droping
182             Benchmarks that are non-scaling outliers (over 2*stdev less than the mean).
183              
184             The result hash return looks like this:
185              
186             %scal = (
187             bench_name => $bench_avg_scalability,
188             ...
189             _total => $total_avg_scalability
190             );
191              
192              
193             =head2 C
194              
195             my ($stats, $stats_multi, $scal) = suite_calc(\%suite_run_options, $keep_outliers?);
196              
197             Convenience function that combines 3 calls, L with C1>,
198             L with Csystem_identity(1)> and L with
199             the results of those two, returning hashrefs with the results of all three calls.
200              
201             For single-core systems (or when C does not return E 1)
202             only C<$stats> will be returned.
203              
204             You can override the C call and run the multi-thread bench with
205             a custom number of threads by passing C [count]>.
206              
207             =head1 BENCHMARK FUNCTIONS
208              
209             The benchmark functions will be called with two parameters that you can choose to
210             take advantage of.
211             The first one is what you define as either the C<$quick_arg> or C<$normal_arg>,
212             with the intention being to have a way to run a C mode that lets you test with
213             smaller workloads. The second argument will be an integer that's the chunk number
214             from L - it will be 1 for the call on the first thread, 2 from the second
215             thread etc, so your function may track which worker/chunk is running.
216              
217             The function may return a string, usually a checksum, that will be checked against
218             the (optional) C<$expected> parameter to show a Pass/Fail (useful for verifying
219             correctness, stress testing, etc.).
220              
221             Example:
222              
223             use Benchmark::MCE;
224             use Math::Trig qw/:great_circle :pi/;
225              
226             sub great_circle {
227             my $size = shift || 1; # Optionally have an argument that scales the workload
228             my $chunk = shift; # Optionally use the chunk number
229             my $dist = 0;
230             $dist +=
231             great_circle_distance(rand(pi), rand(2 * pi), rand(pi), rand(2 * pi))
232             for 1 .. $size;
233             return $dist; # Returning a value is optional for the Pass/Fail functionality
234             }
235              
236             my %stats = suite_run({
237             bench => { 'Math::Trig' => # A unique name for the benchmark
238             [
239             \&great_circle, # Reference to bench function
240             '3144042.81433949', # Reference output - determines Pass/Fail (optional)
241             5.5, # Seconds to complete in normal mode for score = 1000 (optional)
242             1000000, # Argument to pass for quick mode (optional)
243             5000000 # Argument to pass for normal mode (optional)
244             ]},
245             }
246             );
247              
248             =head1 STDOUT / QUIET MODE
249              
250             Normally function calls will print results to C as well as return them.
251             You can suppress STDOUT by setting:
252              
253             $Benchmark::MCE::QUIET = 1;
254              
255             =head1 NOTES
256              
257             The framework uses a monotonic timer for non-Windows systems with at least v1.9764
258             of C (C<$Benchmark::MCE::MONO_CLOCK> will be true).
259              
260             =head1 AUTHOR
261              
262             Dimitrios Kechagias, C<< >>
263              
264             =head1 BUGS
265              
266             Please report any bugs or feature requests on L.
267              
268             =head1 GIT
269              
270             L
271              
272             =head1 LICENSE AND COPYRIGHT
273              
274             Copyright (c) 2025-2026 Dimitrios Kechagias and SpareRoom.
275              
276             This is free software; you can redistribute it and/or modify it under
277             the same terms as the Perl 5 programming language system itself.
278              
279             =cut
280              
281             sub system_identity {
282 39     39 1 327964 my ($physical, $cores, $ncpu) = System::CPU::get_cpu;
283 39   100     10716 $ncpu ||= 1;
284 39 100       233 return $ncpu if @_;
285              
286 2 50       12 local $^O = 'linux' if $^O =~ /android/;
287 2         25 my $info = System::Info->sysinfo_hash;
288 2   0     33986 my $osn = $info->{distro} || $info->{os} || $^O;
289 2   50     31 my $model = System::CPU::get_name || '';
290 2   50     7735 my $arch = System::CPU::get_arch || '';
291 2 50       21275 $arch = " ($arch)" if $arch;
292 2         42 _print("--------------- Software ---------------\n",_package_ver(),"\n");
293             _printf(
294             "Perl $^V (%sthreads, %smulti)\n",
295             $Config{usethreads} ? '' : 'no ',
296 2 50       454 $Config{usemultiplicity} ? '' : 'no '
    50          
297             );
298 2         22 _print("OS: $osn\n--------------- Hardware ---------------\n");
299 2         21 _print("CPU type: $model$arch\n");
300 2         26 _print("CPUs: $ncpu");
301 2         4 my @extra;
302 2 50 33     37 push @extra, "$physical Processors" if $physical && $physical > 1;
303 2 50       35 push @extra, "$cores Cores" if $cores;
304 2 50 33     46 push @extra, "$ncpu Threads" if $cores && $cores != $ncpu;
305 2 50       20 _print(" (".join(', ', @extra).")") if @extra;
306 2         6 _print("\n".("-"x40)."\n");
307              
308 2         133 return $ncpu;
309             };
310              
311             sub suite_calc {
312 64     64 1 279685 my $opt = shift;
313 64         128 my $outliers = shift;
314 64         1188 my %single = suite_run({%$opt, threads => 1});
315 59   66     2661 my $cpus = $opt->{threads} || system_identity(1);
316 59 100       1158 return \%single unless $cpus > 1;
317 41         2436 my %multi = suite_run({%$opt, threads => $cpus});
318 33         1368 return \%single, \%multi, {calc_scalability(\%single, \%multi, $outliers)};
319             }
320              
321             sub suite_run {
322 218     218 1 224450 my $opt = shift;
323 218         3508 _init_options($opt);
324              
325 142         250 my %stats;
326 142         2772 $stats{_opt}->{$_} = $opt->{$_} foreach qw/threads scale iter time no_check/;
327              
328 142 100       1641 my $thread = $opt->{threads} > 1 ? "$opt->{threads}-thread" : "single-thread";
329 142         678 _print(__PACKAGE__, " $thread run");
330 142 100       915 _print($opt->{no_mce} ? " (no MCE):\n" : ":\n");
331              
332             MCE::Loop::init {
333             max_workers => $opt->{threads},
334             chunk_size => 1,
335 142 100       5359 } unless $opt->{no_mce};
336              
337 142 100       5700 if ($opt->{duration}) {
338 1         4 my $t0 = _get_time();
339 1         8 my $cnt = 0;
340 1         3 my $t = 0;
341 1         5 while ($t < $opt->{duration}) {
342 1         2 $cnt++;
343 1         8 _print("Iteration $cnt (".int($t+0.5)."s of $opt->{duration}s)...\n");
344 1         4 _run_iteration($opt, \%stats);
345 1         6 $t = _get_time()-$t0;
346             }
347 1         19 $opt->{iter} = $cnt;
348 1         5 $stats{_opt}->{iter} = $cnt;
349 1         4 $opt->{duration} = 0;
350             } else {
351 141         829 foreach (1..$opt->{iter}) {
352 144 100       482 _print("Iteration $_ of $opt->{iter}...\n") if $opt->{iter} > 1;
353 144         758 _run_iteration($opt, \%stats);
354             }
355             }
356              
357 99 100       670 _total_stats($opt, \%stats) if $opt->{iter} > 1;
358              
359 99         6160 return %stats;
360             }
361              
362             sub calc_scalability {
363 170     170 1 157516 my $stats1 = shift;
364 170         500 my $stats2 = shift;
365 170         232 my $outliers = shift;
366 170         315 my $opt = $stats1->{_opt};
367 170         510 my $opt2 = $stats2->{_opt};
368              
369             die "Different, non-zero thread count expected between runs"
370             if !$opt->{threads}
371             || !$opt2->{threads}
372 170 100 100     2490 || $opt->{threads} == $opt2->{threads};
      100        
373              
374             ($opt, $opt2) = ($stats2->{_opt}, $stats1->{_opt})
375 113 100       411 if $opt->{threads} > $opt2->{threads};
376              
377 113 100       551 die "Same scale expected between runs" if $opt->{scale} != $opt2->{scale};
378              
379 94         626 my $threads = $opt2->{threads} / $opt->{threads};
380 94 100       859 my $display = $opt->{time} ? 'times' : 'scores';
381              
382 94 100       721 $opt->{f} = $opt->{time} ? '%.3f' : '%5.0f';
383 94         216 my ($cnt, @perf, @scal, %scal);
384 94         254 _print( "Multi thread Scalability:\n"
385             . _pad("Benchmark", 24)
386             . _pad("Multi perf xSingle", 24)
387             . _pad("Multi scalability %", 24)
388             . "\n");
389 94         410 foreach my $bench (sort keys %{$stats1}) {
  94         1044  
390 284 100       685 next if $bench eq '_total';
391 228 100 100     1930 next unless $stats1->{$bench}->{times} && $stats2->{$bench}->{times};
392 100         190 $cnt++;
393 100         399 my @res1 = _min_max_avg($stats1->{$bench}->{times});
394 100         288 my @res2 = _min_max_avg($stats2->{$bench}->{times});
395 100 100       735 $scal{$bench} = $res1[2]/$res2[2]*100 if $res2[2];
396 100 100       326 push @perf, $res1[2]/$res2[2]*$threads if $res2[2];
397 100 100       269 push @scal, $scal{$bench} if $scal{$bench};
398 100 100       396 _print( _pad("$bench:", 24)
399             . _pad(sprintf("%.2f", $perf[-1]), 24)
400             . _pad(sprintf("%2.0f", $scal[-1]), 24) . "\n")
401             if @perf;
402             }
403 94 100       661 die "No bench times recorded" unless @perf;
404 56         186 _print(("-"x40)."\n");
405 56         217 my @avg1 = _min_max_avg($stats1->{_total}->{$display});
406 56         176 my @avg2 = _min_max_avg($stats2->{_total}->{$display});
407 56         214 _print(__PACKAGE__, " summary ($cnt benchmark");
408 56 100       495 _print("s") if $cnt > 1;
409 56 100       182 _print(" x$opt->{scale} scale") if $opt->{scale} > 1;
410 56 100       1274 _print(", $opt->{iter} iterations") if $opt->{iter} > 1;
411 56         263 _print(", $opt2->{threads} threads):\n");
412 56 100       251 $opt->{f} .= "s" if $opt->{time};
413 56 100       164 my $f = $opt->{time} ? '%.3f' : '%.0f';
414 56 100       1030 $f = $opt->{iter} > 1 ? "$opt->{f}\t($f - $f)" : $opt->{f};
415 56 100       497 @avg1 = $opt->{iter} > 1 ? ($avg1[2], $avg1[0], $avg1[1]) : ($avg1[2]);
416 56 100       370 @avg2 = $opt->{iter} > 1 ? ($avg2[2], $avg2[0], $avg2[1]) : ($avg2[2]);
417 56         171 _print(_pad("Single:").sprintf($f, @avg1)."\n");
418 56         206 _print(_pad("Multi:").sprintf($f, @avg2)."\n");
419 56 100       641 my @newperf = $outliers ? @perf : _drop_outliers(\@perf, -1);
420 56 100       210 my @newscal = $outliers ? @scal : _drop_outliers(\@scal, -1);
421 56         136 @perf = _min_max_avg(\@newperf);
422 56         140 @scal = _min_max_avg(\@newscal);
423 56         152 $scal{_total} = $scal[2];
424 56         151 _print( _pad("Multi/Single perf:")
425             . sprintf("%.2fx\t(%.2f - %.2f)", $perf[2], $perf[0], $perf[1])
426             . "\n");
427 56         177 _print(
428             _pad("Multi scalability:") . sprintf(
429             "%2.1f%% \t(%.0f%% - %.0f%%)", $scal[2], $scal[0], $scal[1]
430             )
431             . "\n"
432             );
433              
434 56         3614 return %scal;
435             }
436              
437             sub _init_options {
438 218     218   1026 my $opt = shift;
439 218   100     5048 $opt->{iter} ||= $opt->{iterations} || 1;
      66        
440 218   100     2051 $opt->{bench} ||= $opt->{benchmarks} || $opt->{extra_bench};
      100        
441 218 100 100     1982 die "No benchmarks defined" unless $opt->{bench} && %{$opt->{bench}};
  199         989  
442 180         680 foreach my $b (keys %{$opt->{bench}}) {
  180         2485  
443 682 100       1574 if (!ref($opt->{bench}->{$b})) { # string
444 31         3037 my $f = eval "sub { $opt->{bench}->{$b} }";
445 31 100       276 die "Error compiling benchmark '$b': $@" if $@;
446 12         48 $opt->{bench}->{$b} = $f;
447             }
448             $opt->{bench}->{$b} = [$opt->{bench}->{$b}]
449 663 100       2192 if ref($opt->{bench}->{$b}) eq 'CODE'; # wrap coderef
450             die "Error defining benchmark '$b'"
451 663 100       1699 if ref($opt->{bench}->{$b}) ne 'ARRAY';
452             }
453 142   100     768 $opt->{threads} ||= 1;
454 142   100     2965 $opt->{scale} ||= 1;
455 142 100       2194 ($opt->{time}, $opt->{no_check}) = (1, 1) if $opt->{quick};
456 142 100 100     1313 $opt->{scale} = 1 if $opt->{quick} || $opt->{no_mce};
457 142         218 foreach my $arr (values %{$opt->{bench}}) {
  142         424  
458 644 100 100     5810 $opt->{time} = 1 unless scalar(@$arr) > 2 && $arr->[2] && $arr->[2] > 0;
      66        
459 644 100 100     3026 $opt->{no_check} = 1 unless scalar(@$arr) > 1 && defined $arr->[1];
460             }
461 142 100       23759 $opt->{f} = $opt->{time} ? '%.3f' : '%5.0f';
462 142 100       652 $opt->{threads} = 1 if $opt->{no_mce};
463             }
464              
465             sub _run_iteration {
466 145     145   245 my $opt = shift;
467 145         618 my $stats = shift;
468 145         311 my $benchmarks = $opt->{bench};
469 145 100       1012 my $title = $opt->{time} ? 'Time (sec)' : 'Score';
470 145         478 _print(_pad("Benchmark")._pad($title));
471 145 100       711 _print("Pass/Fail") unless $opt->{no_check};
472 145         409 _print("\n");
473 145         628 my ($total_score, $total_time, $i) = (0, 0, 0);
474 145         1420 foreach my $bench (sort keys %$benchmarks) {
475 618 100 100     3477 next if $opt->{filter} && !$opt->{filter}->($opt, $bench, $benchmarks->{$bench});
476 588 100 100     2767 next if $opt->{exclude} && $bench =~ /$opt->{exclude}/;
477 567 100 100     52660 next if $opt->{include} && $bench !~ /$opt->{include}/;
478 164         748 my ($time, $res) = _mce_bench_run($opt, $benchmarks->{$bench});
479             my $score =
480             $benchmarks->{$bench}->[2] && $time
481 146 100 66     3952 ? int(1000 * $opt->{threads} * $benchmarks->{$bench}->[2] / $time + 0.5)
482             : 1;
483 146         797 $total_score += $score;
484 146         326 $total_time += $time;
485 146         301 $i++;
486 146         294 push @{$stats->{$bench}->{times}}, $time;
  146         5555  
487 146         374 push @{$stats->{$bench}->{scores}}, $score;
  146         2183  
488 146 100       1191 my $d = $stats->{$bench}->{$opt->{time} ? 'times' : 'scores'}->[-1];
489 146 100 100     2196 $stats->{$bench}->{fail}++ if !$opt->{quick} && $res ne 'Pass';
490 146         2185 _print(_pad("$bench:")._pad(sprintf($opt->{f}, $d)));
491 146 100       1645 _print("$res") unless $opt->{no_check};
492 146         531 _print("\n");
493 146 100       1001448 sleep $opt->{sleep} if $opt->{sleep};
494             }
495 127 100       4054 die "No benchmarks to run\n" unless $i;
496 102         746 my $s = int($total_score/$i+0.5);
497             _print(_pad("Overall $title: ")
498 102 100       1019 . sprintf($opt->{f} . "\n", $opt->{time} ? $total_time : $s));
499 102         389 push @{$stats->{_total}->{times}}, $total_time;
  102         3140  
500 102         402 push @{$stats->{_total}->{scores}}, $s;
  102         4360  
501             }
502              
503             sub _mce_bench_run {
504 164     164   352 my $opt = shift;
505 164         495 my $benchmark = shift;
506 164         1209 my @bench_copy = @$benchmark;
507 164 100 100     2166 $bench_copy[3] = $bench_copy[4] if scalar(@bench_copy) > 3 && !$opt->{quick};
508 164 100       932 return _bench_run(\@bench_copy, 1, $opt->{srand}) if $opt->{no_mce};
509              
510             my @stats = mce_loop {
511 18     18   12157960 my ($mce, $chunk_ref, $chunk_id) = @_;
512 18         236 for (@{$chunk_ref}) {
  18         113  
513 18         3196 my ($time, $res) = _bench_run(\@bench_copy, $_, $opt->{srand});
514 18         846 MCE->gather([$time, $res]);
515             }
516             }
517 155         5477 (1 .. $opt->{threads} * $opt->{scale});
518              
519 137         85161519 my ($res, $time) = ('Pass', 0);
520 137         555 foreach (@stats) {
521 179         1357 $time += $_->[0];
522 179 100       1187 $res = $_->[1] if $_->[1] ne 'Pass';
523             }
524              
525 137         27458 return $time/$opt->{threads} * $opt->{scale}, $res;
526             }
527              
528             sub _bench_run {
529 46     46   4755309 my $benchmark = shift;
530 46         91 my $chunk_no = shift;
531 46   100     1016 my $srand = shift // 1;
532 46 100       607 srand($srand) if $srand > 0; # For repeatability
533 46         466 my $t0 = _get_time();
534 46         2152 my $out = $benchmark->[0]->($benchmark->[3], $chunk_no);
535 27         770523 my $time = sprintf("%.3f", _get_time()-$t0);
536 27 100 66     2467 my $r = !defined $benchmark->[1]
537             || $out eq $benchmark->[1] ? 'Pass' : "Fail ($out)";
538 27         393 return $time, $r;
539             }
540              
541             sub _total_stats {
542 3     3   9 my $opt = shift;
543 3         7 my $stats = shift;
544 3 100       20 my $display = $opt->{time} ? 'times' : 'scores';
545 3 100       28 my $title = $opt->{time} ? 'Time (sec)' : 'Score';
546             _print( "Aggregates ($opt->{iter} iterations"
547 3 50       24 . ($opt->{threads} > 1 ? ", $opt->{threads} threads" : "") . "):\n"
548             . _pad("Benchmark", 24)
549             . _pad("Avg $title")
550             . _pad("Min $title")
551             . _pad("Max $title"));
552 3 100       15 _print(_pad("stdev %")) if $opt->{stdev};
553 3 100       21 _print(_pad("Pass %")) unless $opt->{no_check};
554 3         25 _print("\n");
555              
556 3         7 foreach my $bench (sort keys %{$opt->{bench}}) {
  3         42  
557 15 100       52 next unless $stats->{$bench}->{$display};
558 7         25 my $str = _calc_stats($opt, $stats->{$bench}->{$display});
559 7         23 _print(_pad("$bench:",24).$str);
560             _print(
561             _pad(
562             sprintf("%d",
563             100 * ($opt->{iter} - ($stats->{$bench}->{fail} || 0)) /
564             $opt->{iter})
565             )
566 7 100 50     58 ) unless $opt->{no_check};
567 7         22 _print("\n");
568             }
569              
570 3         14 my $str = _calc_stats($opt, $stats->{_total}->{$display});
571 3         18 _print(_pad("Overall Avg $title:", 24)."$str\n");
572             }
573              
574             sub _calc_stats {
575 10     10   15 my $opt = shift;
576 10         19 my $arr = shift;
577 10         14 my $pad = shift;
578 10         54 my ($min, $max, $avg) = _min_max_avg($arr);
579 10         29 my $str = join '', map {_pad(sprintf($opt->{f}, $_), $pad)} ($avg,$min,$max);
  30         118  
580 10 100 66     61 if ($opt->{stdev} && $avg) {
581 2         7 my $stdev = _avg_stdev($arr);
582 2         6 $stdev *= 100/$avg;
583 2         12 $str .= _pad(sprintf("%0.2f%%", $stdev), $pad);
584             }
585 10         28 return $avg, $str;
586             }
587              
588             sub _min_max_avg {
589 586     586   820 my $arr = shift;
590 586 100       2007 return (0, 0, 0) unless @$arr;
591 529         2473 return min(@$arr), max(@$arr), sum(@$arr)/scalar(@$arr);
592             }
593              
594             sub _avg_stdev {
595 152     152   210 my $arr = shift;
596 152 100       959 return (0, 0) unless @$arr;
597 133         445 my $sum = sum(@$arr);
598 133         629 my $avg = $sum/scalar(@$arr);
599 133         192 my @sq;
600 133         2372 push @sq, ($avg - $_)**2 for (@$arr);
601 133         338 my $dev = _min_max_avg(\@sq);
602 133         845 return $avg, sqrt($dev);
603             }
604              
605             # $single = single tail of dist curve outlier, 1 for over (right), -1 for under (left)
606             sub _drop_outliers {
607 131     131   43567 my $arr = shift;
608 131         210 my $single = shift;
609 131         335 my ($avg, $stdev) = _avg_stdev($arr);
610 131         204 my @newarr;
611 131         658 foreach (@$arr) {
612 827 100       1363 if ($single) {
613 580 100       1076 push @newarr, $_ unless $single*($_ - $avg) > 2*$stdev;
614             } else {
615 247 100       779 push @newarr, $_ unless abs($avg - $_) > 2*$stdev;
616             }
617             }
618 131         774 return @newarr;
619             }
620              
621             sub _pad {
622 1494     1494   4777 my $str = shift;
623 1494   100     8470 my $len = shift || 20;
624 1494         18523 return $str." "x($len-length($str));
625             }
626              
627             sub _printf {
628 2 100   2   27 printf @_ unless $QUIET;
629             }
630              
631             sub _print {
632 1707 100   1707   190790 print @_ unless $QUIET;
633             }
634              
635             sub _get_time {
636 75 100   75   1538 return $MONO_CLOCK
637             ? Time::HiRes::clock_gettime(CLOCK_MONOTONIC)
638             : Time::HiRes::time();
639             }
640              
641             sub _package_ver {
642 21     21   78 my $pkg = __PACKAGE__;
643 21         77 my $ver = $VERSION;
644              
645 21         71 my $caller = caller(0);
646 21         162 for (my $i = 0; $i < 5; $i++) {
647 29 50       118 my $caller = caller($i) or last;
648 29 100       816 if ($caller eq 'Benchmark::DKbench') {
649 19         76 $pkg = $caller;
650 19   50     57 $ver = eval {$caller->VERSION} || '';
651 19         76 last;
652             }
653             }
654              
655 21         195 return "$pkg v$ver";
656             }
657              
658             1;