File Coverage

blib/lib/Benchmark/DKbench.pm
Criterion Covered Total %
statement 661 697 94.8
branch 151 172 87.7
condition 49 62 79.0
subroutine 86 89 96.6
pod 3 45 6.6
total 950 1065 89.2


line stmt bran cond sub pod time code
1             package Benchmark::DKbench;
2              
3 3     3   669906 use strict;
  3         18  
  3         90  
4 3     3   15 use warnings;
  3         6  
  3         71  
5              
6 3     3   1557 use Digest;
  3         1786  
  3         98  
7 3     3   20 use Digest::MD5 qw(md5_hex);
  3         5  
  3         181  
8 3     3   1468 use Encode;
  3         15903  
  3         260  
9 3     3   1566 use File::Spec::Functions;
  3         2682  
  3         233  
10 3     3   22 use List::Util qw(min max sum);
  3         5  
  3         244  
11 3     3   21 use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
  3         8  
  3         35  
12 3     3   3633 use Time::Piece;
  3         35243  
  3         17  
13              
14 3     3   2317 use Astro::Coord::Constellations 'constellation_for_eq';
  3         60291  
  3         194  
15 3     3   23 use Astro::Coord::Precession 'precess';
  3         6  
  3         125  
16 3     3   2029 use Crypt::JWT qw(encode_jwt decode_jwt);
  3         177911  
  3         229  
17 3     3   2289 use CSS::Inliner;
  3         472028  
  3         121  
18 3     3   2884 use DateTime;
  3         1642969  
  3         176  
19 3     3   5525 use DBI;
  3         56770  
  3         243  
20 3     3   34 use File::ShareDir 'dist_dir';
  3         7  
  3         165  
21 3     3   1714 use HTML::FormatText;
  3         21125  
  3         116  
22 3     3   21 use HTML::TreeBuilder;
  3         9  
  3         49  
23 3     3   3228 use Imager;
  3         154672  
  3         21  
24 3     3   1539 use Imager::Filter::Mandelbrot;
  3         1925  
  3         105  
25 3     3   1831 use Image::PHash;
  3         19220  
  3         104  
26 3     3   24 use JSON::XS;
  3         8  
  3         241  
27 3     3   22 use Math::DCT ':all';
  3         6  
  3         483  
28 3     3   3357 use Math::MatrixReal;
  3         91004  
  3         183  
29 3     3   1929 use MCE::Loop;
  3         153571  
  3         24  
30 3     3   3785 use SQL::Abstract::Classic;
  3         36860  
  3         134  
31 3     3   1554 use SQL::Inserter;
  3         6528  
  3         157  
32 3     3   1574 use System::CPU;
  3         7310  
  3         112  
33 3     3   1461 use System::Info;
  3         55499  
  3         148  
34 3     3   1460 use Text::Levenshtein::Damerau::XS;
  3         2002  
  3         155  
35 3     3   1370 use Text::Levenshtein::XS;
  3         1963  
  3         160  
36              
37 3     3   26 use Exporter 'import';
  3         7  
  3         26741  
38             our @EXPORT = qw(system_identity suite_run calc_scalability);
39             our $datadir = dist_dir("Benchmark-DKbench");
40              
41             our $VERSION = '2.2';
42              
43             =head1 NAME
44              
45             Benchmark::DKbench - Perl CPU Benchmark
46              
47             =head1 SYNOPSIS
48              
49             # Run the suite single-threaded and then multi-threaded on multi-core systems
50             # Will print scores for the two runs and multi/single thread scalability
51             dkbench
52              
53             # A dual-thread "quick" run (with times instead of scores)
54             dkbench -j 2 -q
55              
56             # If BioPerl is installed, enable the BioPerl benchmarks by downloading Genbank data
57             dkbench --setup
58              
59             # Force install the reference versions of all CPAN modules
60             setup_dkbench --force
61              
62             =head1 DESCRIPTION
63              
64             A Perl benchmark suite for general compute, created to evaluate the comparative
65             performance of systems when running computationally intensive Perl (both pure Perl
66             and C/XS) workloads. It is a good overall indicator for generic CPU performance in
67             real-world scenarios. It runs single and multi-threaded (able to scale to hundreds
68             of CPUs) and can be fully customized to run the benchmarks that better suit your own
69             scenario.
70              
71             =head1 INSTALLATION
72              
73             The only non-CPAN software required to install/run the suite is a build environment
74             for the C/XS modules (C compiler, make etc.) and Perl. On the most popular Linux
75             package managers you can easily set up such an environment (as root or with sudo):
76              
77             # Debian/Ubuntu etc
78             apt-get update
79             apt-get install build-essential perl cpanminus
80              
81             # CentOS/Red Hat
82             yum update
83             yum install gcc make patch perl perl-App-cpanminus
84              
85             After that, you can use L<App::cpanminus> to install the benchmark suite (as
86             root/sudo is the easiest, will install for all users):
87              
88             cpanm -n Benchmark::DKbench
89              
90             See the C<setup_dkbench> script below for more on the installation of a couple of
91             optional benchmarks and standardizing your benchmarking environment.
92              
93             =head1 SCRIPTS
94              
95             You will most likely only ever need the main script C<dkbench> which launches the
96             suite, although C<setup_dkbench> can help with setup or standardizing/normalizing your
97             benchmarking environment.
98              
99             =head2 C<dkbench>
100              
101             The main script that runs the DKbench benchmark suite. If L<BioPerl> is installed,
102             you may want to start with C<dkbench --setup>. But beyond that, there are many
103             options to control number of threads, iterations, which benchmarks to run etc:
104              
105             dkbench [options]
106              
107             Options:
108             --threads <i>, -j <i> : Number of benchmark threads (default is 1).
109             --multi, -m : Multi-threaded using all your CPU cores/threads.
110             --max_threads <i> : Override the cpu detection to specify max cpu threads.
111             --iter <i>, -i <i> : Number of suite iterations (with min/max/avg at the end).
112             --include <regex> : Run only benchmarks that match regex.
113             --exclude <regex> : Do not run benchmarks that match regex.
114             --time, -t : Report time (sec) instead of score.
115             --quick, -q : Quick benchmark run (implies -t).
116             --no_mce : Do not run under MCE::Loop (implies -j 1).
117             --skip_bio : Skip BioPerl benchmarks.
118             --skip_prove : Skip Moose prove benchmark.
119             --time_piece : Run optional Time::Piece benchmark (see benchmark details).
120             --bio_codons : Run optional BioPerl Codons benchmark (does not scale well).
121             --sleep <i> : Sleep for <i> secs after each benchmark.
122             --setup : Download the Genbank data to enable the BioPerl tests.
123             --datapath <path> : Override the path where the expected benchmark data is found.
124             --ver <num> : Skip benchmarks added after the specified version.
125             --help -h : Show basic help and exit.
126              
127             The default run (no options) will run all the benchmarks both single-threaded and
128             multi-threaded (using all detected CPU cores/hyperthreads) and show you scores and
129             multi vs single threaded scalability.
130              
131             The scores are calibrated such that a reference CPU (Intel Xeon Platinum 8481C -
132             Sapphire Rapids) would achieve a score of 1000 in a single-core benchmark run using
133             the default software configuration (Linux/Perl 5.36.0 with reference CPAN modules).
134              
135             The multi-thread scalability should approach 100% if each thread runs on a full core
136             (i.e. no SMT), and the core can maintain the clock speed it had on the single-thread
137             runs. Note that the overall scalability is an average of the benchmarks that drops
138             non-scaling outliers (over 2*stdev less than the mean).
139              
140             The suite will report a Pass/Fail per benchmark. A failure may be caused if you have
141             different CPAN module version installed - this is normal, and you will be warned.
142              
143             The suite uses L<MCE::Loop> to run on the desired number of parallel threads, although
144             there is an option to disable it, which forces a single-thread run.
145              
146             =head2 C<setup_dkbench>
147              
148             Simple installer to check/get the reference versions of CPAN modules and download
149             the Genbank data file required for the BioPerl benchmarks of the DKbench suite.
150              
151             It assumes that you have some software already installed (see INSTALLATION above),
152             try C<setup_dkbench --help> will give you more details.
153              
154             setup_dkbench [--force --sudo --test --data=s --help]
155              
156             Options:
157             --sudo : Will use sudo for cpanm calls.
158             --force : Will install reference CPAN module versions and re-download the genbank data.
159             --test : Will run the test suites for the CPAN module (default behaviour is to skip).
160             --data=s : Data dir path to copy files from. Should not need if you installed DKbench.
161             --help : Print this help text and exit.
162              
163             Running it without any options will fetch the data for the BioPerl tests (similar to
164             C<dkbench --setup>) and use C<cpanm> to install any missing libraries.
165              
166             Using it with C<--force> will install the reference CPAN module versions, including
167             BioPerl which is not a requirement for DKbench, but enables the BioPerl benchmarks.
168              
169             The reference Perl and CPAN versions are suggested if you want a fair comparison
170             between systems and also for the benchmark Pass/Fail results to be reliable.
171              
172             =head1 BENCHMARKS
173              
174             The suite consists of 21 benchmarks, 19 will run by default. However, the
175             C<BioPerl Monomers> requires the optional L<BioPerl> to be installed and Genbank
176             data to be downloaded (C<dkbench --setup> can do the latter), so you will only
177             see 18 benchmarks running just after a standard install. Because the overall score
178             is an average, it is generally unaffected by adding or skipping a benchmark or two.
179              
180             The optional benchmarks are enabled with the C<--time_piece> and C<--bio_codons>
181             options.
182              
183             =over 4
184              
185             =item * C<Astro> : Calculates precession between random epochs and finds the
186             constellation for random equatorial coordinates using L<Astro::Coord::Precession>
187             and L<Astro::Coord::Constellations> respectively.
188              
189             =item * C<BioPerl Codons> : Counts codons on a sample bacterial sequence. Requires
190             L<BioPerl> to be installed.
191             This test does not scale well on multiple threads, so is disabled by default (use
192             C<--bio_codons>) option. Requires data fetched using the C<--setup> option.
193              
194             =item * C<BioPerl Monomers> : Counts monomers on 500 sample bacterial sequences using
195             L<BioPerl> (which needs to be installed). Requires data fetched using the C<--setup>
196             option.
197              
198             =item * C<CSS::Inliner> : Inlines CSS on 2 sample wiki pages using L<CSS::Inliner>.
199              
200             =item * C<Crypt::JWT> : Creates large JSON Web Tokens with RSA and EC crypto keys
201             using L<Crypt::JWT>.
202              
203             =item * C<DateTime> : Creates and manipulates L<DateTime> objects.
204              
205             =item * C<DBI/SQL> : Creates a mock L<DBI> connection (using L<DBD::Mock>) and passes
206             it insert/select statements using L<SQL::Inserter> and L<SQL::Abstract::Classic>.
207             The latter is quite slow at creating the statements, but it is widely used.
208              
209             =item * C<Digest> : Creates MD5, SH1 and SHA-512 digests of a large string.
210              
211             =item * C<Encode> : Encodes/decodes large strings from/to UTF-8/16, cp-1252.
212              
213             =item * C<HTML::FormatText> : Converts HTML to text for 2 sample wiki pages using
214             L<HTML::FormatText>.
215              
216             =item * C<Imager> : Loads a sample image and performs edits/manipulations with
217             L<Imager>, including filters like gaussian, unsharp mask, mandelbrot.
218              
219             =item * C<JSON::XS> : Encodes/decodes random data structures to/from JSON using
220             L<JSON::XS>.
221              
222             =item * C<Math::DCT> : Does 8x8, 18x18 and 32x32 DCT transforms with L<Math::DCT>.
223              
224             =item * C<Math::MatrixReal> : Performs various manipulations on L<Math::MatrixReal>
225             matrices.
226              
227             =item * C<Moose> : Creates L<Moose> objects.
228              
229             =item * C<Moose prove> : Runs 110 tests from the Moose 2.2201 test suite. The least
230             CPU-intensive test (which is why there is the option C<--no_prove> to disable it),
231             most of the time will be spent loading the interpreter and the Moose module for each
232             test, which is behaviour representative of how a Perl test suite runs by default.
233              
234             =item * C<Primes> : Calculates all primes up to 7.5 million. Small number with
235             repeat was chosen to keep low memory (this is a pure Perl function no Math libraries).
236              
237             =item * C<Regex/Subst> : Concatenates 3 wiki pages into a byte string then matches
238             3 typical regexes (for names, emails, URIs), replaces html tags with their contents
239             (starting with the innermost) and does calls subst a few times.
240              
241             =item * C<Regex/Subst utf8> : Exactly the same as C<Regex/Subst>, but reads into
242             a utf8 string. Perl version can make a big difference, as Unicode behaviour has
243             changed (old Perl versions are faster but less strict in general).
244              
245             =item * C<Text::Levenshtein> : The edit distance for strings of various lengths (up
246             to 2500) are calculated using L<Text::Levenshtein::XS> and L<Text::Levenshtein::Damerau::XS>.
247              
248             =item * C<Time::Piece> : Creates and manipulates/converts Time::Piece objects. It
249             is disabled by default because it uses the OS time libraries, so it might skew results
250             if you are trying to compare CPUs on different OS platforms. It can be enabled with
251             the C<--time_piece> option. For MacOS specifically, it can only be enabled if C<--no_mce>
252             is specified, as it runs extremely slow when forked.
253              
254             =back
255              
256             =head1 EXPORTED FUNCTIONS
257              
258             You will normally not use the Benchmark::DKbench module itself, but here are the
259             exported functions that the C<dkbench> script uses for reference:
260              
261             =head2 C<system_identity>
262              
263             my $cores = system_identity();
264              
265             Prints out software/hardware configuration and returns then number of cores detected.
266              
267             =head2 C<suite_run>
268              
269             my %stats = suite_run(\%options);
270              
271             Runs the benchmark suite given the C<%options> and prints results. Returns a hash
272             with run stats.
273              
274             =head2 C<calc_scalability>
275              
276             calc_scalability(\%options, \%stat_single, \%stat_multi);
277              
278             Given the C<%stat_single> results of a single-threaded C<suite_run> and C<%stat_multi>
279             results of a multi-threaded run, will calculate and print the multi-thread scalability.
280              
281             =head1 NOTES
282              
283             The benchmark suite was created to compare the performance of various cloud offerings.
284             You can see the L<original perl blog post|http://blogs.perl.org/users/dimitrios_kechagias/2022/03/cloud-provider-performance-comparison-gcp-aws-azure-perl.html>
285             as well as the L<2023 follow-up|https://dev.to/dkechag/cloud-vm-performance-value-comparison-2023-perl-more-1kpp>.
286              
287             The benchmarks for the first version were more tuned to what I would expect to run
288             on the servers I was testing, in order to choose the optimal types for the company
289             I was working for. The second version has expanded a bit over that, and is friendlier
290             to use.
291              
292             Althought this benchmark is in general a good indicator of general CPU performance
293             and can be customized to your needs, no benchmark is as good as running your own
294             actual workload.
295              
296             =head2 SCORES
297              
298             Some sample DKbench score results from various systems for comparison (all on
299             reference setup with Perl 5.36.0):
300              
301             CPU Cores/HT Single Multi Scalability
302             Intel i7-4750HQ @ 2.0 (MacOS) 4/8 612 2332 46.9%
303             AMD Ryzen 5 PRO 4650U @ 2.1 (WSL) 6/12 905 4444 40.6%
304             Apple M1 Pro @ 3.2 (MacOS) 10/10 1283 10026 78.8%
305             Apple M2 Pro @ 3.5 (MacOS) 12/12 1415 12394 73.1%
306             Ampere Altra @ 3.0 (Linux) 48/48 708 32718 97.7%
307             Intel Xeon Platinum 8481C @ 2.7 (Linux) 88/176 1000 86055 48.9%
308             AMD EPYC Milan 7B13 @ 2.45 (Linux) 112/224 956 104536 49.3%
309             AMD EPYC Genoa 9B14 @ 2.7 (Linux) 180/360 1197 221622 51.4%
310              
311             =head1 AUTHOR
312              
313             Dimitrios Kechagias, C<< <dkechag at cpan.org> >>
314              
315             =head1 BUGS
316              
317             Please report any bugs or feature requests either on L<GitHub|https://github.com/dkechag/Benchmark-DKbench> (preferred), or on RT (via the email
318             C<bug-Benchmark-DKbench at rt.cpan.org> or L<web interface|https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Benchmark-DKbench>).
319              
320             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
321              
322             =head1 GIT
323              
324             L<https://github.com/dkechag/Benchmark-DKbench>
325              
326             =head1 LICENSE AND COPYRIGHT
327              
328             This software is copyright (c) 2021-2023 by Dimitrios Kechagias.
329              
330             This is free software; you can redistribute it and/or modify it under
331             the same terms as the Perl 5 programming language system itself.
332              
333             =cut
334              
335             sub benchmark_list {
336             return { # idx : 0 = result, 1 = ref time, 2 = func, 3 = quick test, 4 = normal test, 5 = ver
337 12     12 0 683 'Astro' => ['e71c7ae08f16fe26aea7cfdb72785873', 5.674, \&bench_astro, 20000, 80000],
338             'BioPerl Codons' => ['97c443c099886ca60e99f7ab9df689b5', 8.752, \&bench_bioperl_codons, 3, 5, 1],
339             'BioPerl Monomers' => ['d29ed0a5c205c803c112be1338d1f060', 5.241, \&bench_bioperl_mono, 6, 20],
340             'Crypt::JWT' => ['d41d8cd98f00b204e9800998ecf8427e', 6.451, \&bench_jwt, 250, 900],
341             'CSS::Inliner' => ['82c1b6de9ca0500a48f8a8df0998df3c', 4.603, \&bench_css, 2, 5],
342             'DBI/SQL' => ['2b8252daad9568a5b39038c696df4be3', 5.700, \&bench_dbi, 5000, 15000, 2.1],
343             'DateTime' => ['b08d2eeb994083b7422f6c9d86fed2c6', 6.198, \&bench_datetime, 5000, 15000],
344             'Digest' => ['4b69f6cf0f53cbf6c3444f2f767dd21d', 4.513, \&bench_digest, 50, 250],
345             'Encode' => ['PASS 1025', 5.725, \&bench_encode, 40, 120],
346             'HTML::FormatText' => ['8c2589f0a5276252805e11301fc2ab56', 4.756, \&bench_formattext, 4, 10],
347             'Imager' => ['8829cb3703e884054eb025496f336c63', 6.792, \&bench_imager, 4, 16],
348             'JSON::XS' => ['PASS', 5.388, \&bench_json, 600, 2200],
349             'Math::DCT' => ['766e3bfd7a2276f452bb3d1bd21939bc', 7.147, \&bench_dct, 25000, 100_000],
350             'Math::MatrixReal' => ['4606231b1309fb21ae1223fa0043fd76', 4.293, \&bench_matrixreal, 200, 650],
351             'Moose' => ['d1cb92c513f6378506dfa11f694cffac', 4.968, \&bench_moose, 10_000, 30_000],
352             'Moose prove' => ['PASS', 7.974, \&bench_moose_prv, 0.5, 1],
353             'Primes' => ['4266f70a7a9efb3484cf5d98eba32244', 3.680, \&bench_primes_m, 2, 5],
354             'Regex/Subst' => ['30ce365b25f3d597578b3bdb14aa3f57', 4.652, \&bench_regex_asc, 8, 24],
355             'Regex/Subst utf8' => ['857eb4e63a4d174ca4a16fe678f7626f', 5.703, \&bench_regex_utf8, 3, 10],
356             'Text::Levenshtein' => ['2948a300ed9131fa0ce82bb5eabb8ded', 5.539, \&bench_textlevenshtein, 7, 25, 2.1],
357             'Time::Piece' => ['2d4b149fe7f873a27109fc376d69211b', 5.907, \&bench_timepiece, 75_000, 275_000],
358             };
359             }
360              
361             sub system_identity {
362 1     1 1 1653 my ($physical, $cores, $ncpu) = System::CPU::get_cpu;
363 1   50     2845 $ncpu ||= 1;
364 1 50       7 local $^O = 'linux' if $^O =~ /android/;
365 1         11 my $info = System::Info->sysinfo_hash;
366 1   0     23214 my $osn = $info->{distro} || $info->{os} || $^O;
367 1   50     29 my $model = System::CPU::get_name || '';
368 1   50     4772 my $arch = System::CPU::get_arch || '';
369 1 50       11340 $arch = " ($arch)" if $arch;
370 1         46 print "--------------- Software ---------------\nDKbench v$VERSION\n";
371 1         49 print "Perl $^V\n";
372 1         26 print "OS: $osn\n--------------- Hardware ---------------\n";
373 1         298 print "CPU type: $model$arch\n";
374 1         15 print "CPUs: $ncpu";
375 1         14 my @extra;
376 1 50 33     105 push @extra, "$physical Processors" if $physical && $physical > 1;
377 1 50       75 push @extra, "$cores Cores" if $cores;
378 1 50 33     58 push @extra, "$ncpu Threads" if $cores && $cores != $ncpu;
379 1 50       34 print " (".join(', ', @extra).")" if @extra;
380 1         11 print "\n".("-"x40)."\n";
381              
382 1         110 return $ncpu;
383             };
384              
385             sub suite_run {
386 6     6 1 35694 my $opt = shift;
387 6 100       67 $datadir = $opt->{datapath} if $opt->{datapath};
388 6   100     58 $opt->{threads} //= 1;
389 6   100     60 $opt->{repeat} //= 1;
390 6 100       41 $opt->{f} = $opt->{time} ? '%.3f' : '%5.0f';
391 6         31 my %stats = (threads => $opt->{threads});
392              
393             MCE::Loop::init {
394             max_workers => $opt->{threads},
395             chunk_size => 1,
396 6 100       79 } unless $opt->{no_mce};
397              
398 6         155 foreach (1..$opt->{iter}) {
399 8 100       173 print "Iteration $_ of $opt->{iter}...\n" if $opt->{iter} > 1;
400 8         53 run_iteration($opt, \%stats);
401             }
402              
403 5 100       63 total_stats($opt, \%stats) if $opt->{iter} > 1;
404              
405 5         910 return %stats;
406             }
407              
408             sub calc_scalability {
409 2     2 1 7965 my ($opt, $stats1, $stats2) = @_;
410 2         60 my $benchmarks = benchmark_list();
411 2         28 my $threads = $stats2->{threads}/$stats1->{threads};
412 2 100       39 my $display = $opt->{time} ? 'times' : 'scores';
413 2 100       31 $opt->{f} = $opt->{time} ? '%.3f' : '%5.0f';
414 2         28 my (@perf, @scal);
415 2         25 print "Multi thread Scalability:\n".pad_to("Benchmark",24).pad_to("Multi perf xSingle",24).pad_to("Multi scalability %",24);
416 2         153 print "\n";
417 2         27 my $cnt;
418 2         56 foreach my $bench (sort keys %$benchmarks) {
419 42 100 100     374 next unless $stats1->{$bench}->{times} && $stats2->{$bench}->{times};
420 2         22 $cnt++;
421 2         32 my @res1 = min_max_avg($stats1->{$bench}->{times});
422 2         30 my @res2 = min_max_avg($stats2->{$bench}->{times});
423 2 50       43 push @perf, $res1[2]/$res2[2]*$threads if $res2[2];
424 2 50       34 push @scal, $res1[2]/$res2[2]*100 if $res2[2];
425 2         42 print pad_to("$bench:",24).pad_to(sprintf("%.2f",$perf[-1]),24).pad_to(sprintf("%2.0f",$scal[-1]),24)."\n";
426             }
427 2         181 print (("-"x40)."\n");
428 2         59 my $avg1 = min_max_avg($stats1->{total}->{$display});
429 2         40 my $avg2 = min_max_avg($stats2->{total}->{$display});
430 2         148 print "DKbench summary ($cnt benchmarks, $stats2->{threads} threads):\n";
431 2         28 print pad_to("Single:").sprintf($opt->{f}, $avg1)."\n";
432 2         49 print pad_to("Multi:").sprintf($opt->{f}, $avg2)."\n";
433 2         38 my @newperf = Benchmark::DKbench::drop_outliers(\@perf, -1);
434 2         34 my @newscal = Benchmark::DKbench::drop_outliers(\@scal, -1);
435 2         35 @perf = min_max_avg(\@newperf);
436 2         52 @scal = min_max_avg(\@newscal);
437 2         50 print pad_to("Multi/Single perf:").sprintf("%.2fx\t(%.2f - %.2f)", $perf[2], $perf[0], $perf[1])."\n";
438 2         34 print pad_to("Multi scalability:").sprintf("%2.1f%% \t(%.0f%% - %.0f%%)", $scal[2], $scal[0], $scal[1])."\n";
439             }
440              
441             sub run_iteration {
442 8     8 0 56 my ($opt, $stats) = @_;
443 8         53 my $benchmarks = benchmark_list();
444 8 100       56 my $title = $opt->{time} ? 'Time (sec)' : 'Score';
445 8         67 print pad_to("Benchmark").pad_to($title);
446 8 100       101 print "Pass/Fail" unless $opt->{time};
447 8         96 print "\n";
448 8         92 my ($total_score, $total_time, $i) = (0, 0, 0);
449 8         147 foreach my $bench (sort keys %$benchmarks) {
450 168 100 100     831 next if $opt->{skip_bio} && $bench =~ /Monomers/;
451 166 100 100     557 next if $opt->{skip_prove} && $bench =~ /prove/;
452 164 100 100     764 next if !$opt->{bio_codons} && $bench =~ /Codons/;
453 158 100 100     693 next if !$opt->{time_piece} && $bench =~ /Time::Piece/;
454 152 100 100     615 next if $opt->{ver} && $benchmarks->{$bench}->[5] && $opt->{ver} < $benchmarks->{$bench}->[5];
      100        
455 150 100 100     700 next if $opt->{exclude} && $bench =~ /$opt->{exclude}/;
456 144 100 100     990 next if $opt->{include} && $bench !~ /$opt->{include}/;
457 23 50       157 if ($bench =~ /Bio/) {
458 0         0 require Bio::SeqIO;
459 0         0 require Bio::Tools::SeqStats;
460             }
461 23         141 my ($time, $res) = mce_bench_run($opt, $benchmarks->{$bench});
462 23   50     523 my $score = int(1000*$opt->{threads}*$benchmarks->{$bench}->[1]/($time || 1)+0.5);
463 23         127 $total_score += $score;
464 23         88 $total_time += $time;
465 23         72 $i++;
466 23         72 push @{$stats->{$bench}->{times}}, $time;
  23         404  
467 23         94 push @{$stats->{$bench}->{scores}}, $score;
  23         265  
468 23 100       267 my $d = $stats->{$bench}->{$opt->{time} ? 'times' : 'scores'}->[-1];
469 23 100       174 $stats->{$bench}->{fail}++ if $res ne 'Pass';
470 23         297 print pad_to("$bench:").pad_to(sprintf($opt->{f}, $d));
471 23 100       297 print "$res" unless $opt->{time};
472 23         351 print "\n";
473 23 50       257 sleep $opt->{sleep} if $opt->{sleep};
474             }
475 8 100       418 die "No tests to run\n" unless $i;
476 7         51 my $s = int($total_score/$i+0.5);
477 7 100       77 print pad_to("Overall $title: ").sprintf($opt->{f}."\n", $opt->{time} ? $total_time : $s);
478 7         59 push @{$stats->{total}->{times}}, $total_time;
  7         131  
479 7         40 push @{$stats->{total}->{scores}}, $s;
  7         1251  
480             }
481              
482             sub mce_bench_run {
483 23     23 0 87 my $opt = shift;
484 23         72 my $benchmark = shift;
485 23 100       138 $benchmark->[3] = $benchmark->[4] unless $opt->{quick};
486 23 100       143 return bench_run($benchmark) if $opt->{no_mce};
487              
488             my @stats = mce_loop {
489 0     0   0 my ($mce, $chunk_ref, $chunk_id) = @_;
490 0         0 for (@{$chunk_ref}) {
  0         0  
491 0         0 my ($time, $res) = bench_run($benchmark);
492 0         0 MCE->gather([$time, $res]);
493             }
494             }
495 1         37 (1 .. $opt->{threads} * $opt->{repeat});
496              
497 1         20075905 my ($res, $time) = ('Pass', 0);
498 1         34 foreach (@stats) {
499 2         32 $time += $_->[0];
500 2 50       45 $res = $_->[1] if $_->[1] ne 'Pass';
501             }
502              
503 1   50     57 return $time/($opt->{threads}*$opt->{repeat} || 1), $res;
504             }
505              
506             sub bench_run {
507 23     23 0 207 my ($benchmark, $srand) = @_;
508 23   100     195 $srand //= 1;
509 23         83 srand($srand); # For repeatability
510 23         196 my $t0 = clock_gettime(CLOCK_MONOTONIC);
511 23         328 my $out = $benchmark->[2]->($benchmark->[3]);
512 22         42014 my $time = sprintf("%.3f", clock_gettime(CLOCK_MONOTONIC)-$t0);
513 22 100       990 my $r = $out eq $benchmark->[0] ? 'Pass' : "Fail ($out)";
514 22         351 return $time, $r;
515             }
516              
517             sub bench_astro {
518 1     1 0 3 my $iter = shift;
519 1         48 my $d = Digest->new("MD5");
520             my $precessed = precess([rand(24), rand(180)-90], rand(200)+1900, rand(200)+1900)
521 1         87 for (1..$iter*10);
522 1         11212441 my $constellation_abbrev;
523             $d->add(constellation_for_eq(rand(24), rand(180)-90, rand(200)+1900))
524 1         26 for (1..$iter);
525 1         8565950 return $d->hexdigest;
526             }
527              
528             sub bench_bioperl_codons {
529 0     0 0 0 my $skip = shift;
530 0         0 my $iter = shift;
531 0         0 my $d = Digest->new("MD5");
532 0         0 my $file = catfile($datadir, "gbbct5.seq");
533 0         0 foreach (1..$iter) {
534 0         0 my $in = Bio::SeqIO->new(-file => $file, -format => "genbank");
535 0         0 $in->next_seq for (1..$skip);
536 0         0 my $seq = $in->next_seq;
537 0         0 my $seq_stats = Bio::Tools::SeqStats->new($seq);
538 0         0 my $codon_ref = $seq_stats->count_codons();
539 0         0 $d->add($_, $codon_ref->{$_}) for sort keys %$codon_ref;
540             }
541 0         0 return $d->hexdigest;
542             }
543              
544             sub bench_bioperl_mono {
545 0     0 0 0 my $iter = shift;
546 0         0 my $file = catfile($datadir, "gbbct5.seq");
547 0         0 my $in = Bio::SeqIO->new(-file => $file, -format => "genbank");
548 0         0 my $d = Digest->new("MD5");
549 0         0 my $builder = $in->sequence_builder();
550 0         0 $builder->want_none();
551 0         0 $builder->add_wanted_slot('display_id','seq');
552 0         0 for (1..$iter) {
553 0         0 my $seq = $in->next_seq;
554 0         0 my $seq_stats = Bio::Tools::SeqStats->new($seq);
555 0         0 my $weight = $seq_stats->get_mol_wt();
556 0         0 $d->add(int($weight->[0]));
557 0         0 my $monomer_ref = $seq_stats->count_monomers();
558 0         0 $d->add($_, $monomer_ref->{$_}) for sort keys %$monomer_ref;
559             }
560 0         0 return $d->hexdigest;
561             }
562              
563             sub bench_css {
564 1     1 0 5 my $iter = shift;
565 1         10 my $d = Digest->new("MD5");
566 1         67 my $file;
567             my $html;
568 1         5 for (1..$iter) {
569 2         3654 my $inliner = new CSS::Inliner();
570 2         2613 my $i = $_ % 2 + 1;
571 2         36 $file = catfile($datadir, "wiki$i.html");
572 2         36 $inliner->read_file({ filename => $file });
573 2         15657169 $html = $inliner->inlinify();
574 2         16411590 $d->add(Encode::encode_utf8($html));
575             }
576 1         28623 return $d->hexdigest;
577             }
578              
579             sub bench_datetime {
580 1     1 0 5 my $iter = shift;
581 1         5 my @tz = map {DateTime::TimeZone->new( name => $_ )} qw(UTC Europe/London America/New_York);
  3         20923  
582 1         3968 my $d = Digest->new("MD5");
583 1         40 my $str;
584              
585 1         6 for (1..$iter) {
586 5000         136361 my $dt = DateTime->now();
587 5000         1393721 my $dt1 = DateTime->from_epoch(
588             epoch => 946684800 + rand(100000000),
589             );
590 5000         1425841 my $dt2 = DateTime->from_epoch(
591             epoch => 946684800 + rand(100000000),
592             );
593 5000         1396687 $str = $dt2->strftime('%FT%T')."\n";
594 5000         585812 $d->add($str);
595 5000         9671 eval {$dt2->set_time_zone($tz[int(rand(3))])};
  5000         19991  
596 5000         1136416 my $dur = $dt2->subtract_datetime($dt1);
597 5000         6368308 eval {$dt2->add_duration($dur)};
  5000         18226  
598 5000         6342937 eval {$dt2->subtract(days => int(rand(1000)+1))};
  5000         25096  
599 5000         6898194 $dt->week;
600 5000         132509 $dt->epoch;
601 5000         41377 $d->add($dt2->strftime('%FT%T'));
602 5000         613718 eval {$dt2->set( year => int(rand(2030)))};
  5000         20882  
603 5000         3419825 $d->add($dt2->ymd('/'));
604             }
605 1         41 return $d->hexdigest;
606             }
607              
608             sub bench_dbi {
609 1     1 0 5 my $iter = shift;
610 1         10 my $d = Digest->new("MD5");
611 1         132 my $dbh = DBI->connect( 'DBI:Mock:', '', '' );
612 1         21907 my ($data, $cols) = _db_data();
613              
614 1         8 foreach (1..$iter) {
615 5000         2311088 my $inserter = SQL::Inserter->new(
616             dbh => $dbh,
617             table => 'table',
618             cols => $cols,
619             buffer => 2
620             );
621 5000         167151 $inserter->insert($data->[int(rand(20))]) for 1..2;
622 5000         3731160 $d->add($dbh->last_insert_id);
623 5000         57137 my $sql = SQL::Abstract::Classic->new();
624 5000         231392 my ($stmt, @bind) = $sql->insert('table', $data->[int(rand(20))]);
625 5000         3921510 $d->add($dbh->quote($stmt));
626 5000         87662 ($stmt, @bind) = $sql->select('table', $cols->[int(rand(20))], [map {_rand_where()} 1..int(rand(3)+1)]);
  10091         22951  
627 5000         6048879 $d->add($dbh->quote($stmt._random_str(5)));
628 5000         89630 my $dbh2 = DBI->connect( 'DBI:Mock:', '', '' );
629             }
630 1         522 return $d->hexdigest;
631             }
632              
633             sub bench_dct {
634 2     2 0 21 my $iter = shift;
635 2         40 my $d = Digest->new("MD5");
636 2         136 $d->add(bench_dct_sz(@$_)) foreach [$iter, 32], [$iter, 18], [$iter*8,8];
637              
638 2         61 return $d->hexdigest;
639             }
640              
641             sub bench_dct_sz {
642 6     6 0 48 my $iter = shift;
643 6         41 my $sz = shift;
644 6         77 my $d = Digest->new("MD5");
645 6         333 my @arrays;
646 6         176 push @arrays, [map { rand(256) } ( 1..$sz*$sz )] foreach 1..10;
  28240         39996  
647 6         58 foreach (1..$iter) {
648 500000         1028983 my $dct = dct2d($arrays[$iter % 10], $sz);
649 500000 100       16842282 $d->add($dct->[0]) if $_ % 10 == 1;
650             }
651 6         1184 return $d->hexdigest;
652             }
653              
654             sub bench_digest {
655 1     1 0 5 my $iter = shift;
656 1         9 my $str = _read_wiki_files();
657 1         15 my $d = Digest->new("MD5");
658 1         76 my $hex;
659 1         6 foreach (1..$iter) {
660 50         250 my $d2 = Digest->new("MD5");
661 50         430433 $d2->add($str);
662 50         500 $hex = $d2->hexdigest;
663 50         228 $d->add($hex);
664 50         234 $d2 = Digest->new("SHA-512");
665 50         1133356 $d2->add($str);
666 50         765 $hex = $d2->hexdigest;
667 50         286 $d->add($hex);
668 50         258 $d2 = Digest->new("SHA-1");
669 50         661487 $d2->add($str);
670 50         606 $hex = $d2->hexdigest;
671 50         435 $d->add($hex);
672             }
673 1         16 return $d->hexdigest;
674             }
675              
676             sub bench_encode {
677 1     1 0 4 my $iter = shift;
678 1         6 my $str = _read_wiki_files('utf8');
679 1         13 my $UTF8 = Encode::find_encoding('UTF-8');
680 1         73 my $UTF16 = Encode::find_encoding('UTF-16');
681 1         4079 our $cp1252 = Encode::find_encoding('cp-1252');
682 1         452 my $res = 'PASS';
683 1         7 my $unenc = 0;
684              
685 1         7 foreach (1..$iter) {
686 40         52312 my $bytes = encode_utf8($str);
687 40 50       16586 $res = 'Fail' unless length($bytes) > length($str);
688 40         51365 my $cp = decode_utf8($bytes);
689 40 100       177800 my $enc = rand(1) > 0.25 ? $UTF8 : $UTF16;
690 40         860014 $bytes = $enc->encode($cp);
691 40         526287 $cp = $enc->decode($bytes);
692 40 50       112813 $res = 'Fail' unless $cp eq $str;
693 40         942842 my $str2 = $cp1252->encode($cp);
694 40         1855799 $enc->encode($cp1252->decode($str2));
695 40         42764 $unenc = () = $str2 =~ /\?/g; # Non-encodable
696             }
697 1         19 return "$res $unenc";
698             }
699              
700             sub bench_imager {
701 2     2 0 20 my $iter = shift;
702 2         25 my $d = Digest->new("MD5");
703              
704 2         118 my $data;
705 2 100       281 open (my $fh, '<:raw', catfile($datadir,'M31.bmp')) or die $!;
706 1         2634 read($fh, $data, -s $fh);
707 1         24 close($fh);
708              
709 1         7 foreach (1..$iter) {
710 4 50       532138 my $img = Imager->new(data=>$data, type=>'bmp') or die Imager->errstr();
711 4         104072 my $thumb = $img->scale(scalefactor=>.3);
712 4         297307 my $newimg = $img->scale(scalefactor=>1.15);
713 4         682490 $newimg->filter(type=>'autolevels');
714 4         77886 $newimg->filter(type=>"gaussian", stddev=>0.5);
715 4         638113 $newimg->paste(left=>40,top=>20,img=>$thumb);
716 4         3265 $newimg->rubthrough(src=>$thumb,tx=>30, ty=>50);
717 4         2923 $newimg->compose(src => $thumb, tx => 30, ty => 20, combine => 'color');
718 4         36580 $newimg->flip(dir=>"h");
719 4         37467 $newimg->flip(dir=>"vh");
720 4         36622 $d->add(scalar(Image::PHash->new($newimg)->pHash));
721 4         156688 $newimg = $img->crop(left=>50, right=>100, top=>10, bottom=>100);
722 4         1406 $newimg = $img->crop(left=>50, top=>10, width=>50, height=>90);
723 4         768 $newimg = $img->copy();
724 4         29248 $newimg->filter(type=>"unsharpmask", stddev=>1, scale=>0.5);
725 4         737172 $newimg = $img->rotate(degrees=>20);
726 4         403976 $newimg->filter(type=>"contrast", intensity=>1.4);
727 4         135896 $newimg = $img->convert(matrix => [[0, 1, 0], [1, 0, 0], [0, 0, 1]]);
728 4         49072 $newimg = $img->convert(preset=>'grey');
729 4         44788 $d->add(scalar(Image::PHash->new($newimg)->pHash));
730 4         134107 $img->filter(type=>'mandelbrot');
731             }
732 1         177354 return $d->hexdigest;
733             }
734              
735             sub bench_json {
736 1     1 0 4 my $iter = shift;
737 1         7 my $res = 'PASS';
738 1         6 for (1..$iter) {
739 600         1927 my $len = int(rand(40)) + 1;
740 600         1390 my $obj = rand_hash($len);
741 600         25980 my $str = encode_json($obj);
742 600         1467 foreach (1..100) {
743 60000         2540715 $obj = decode_json($str);
744 60000         2147513 $str = encode_json($obj);
745             }
746 600         23370 my $obj2 = decode_json($str);
747 600 50       2176 $res = 'FAIL' unless compare_obj($obj, $obj2);
748             }
749 1         11 return $res;
750             }
751              
752             sub bench_jwt {
753 1     1 0 5 my $iter = shift;
754 1         9 my $d = Digest->new("MD5");
755 1         72 my $data = _random_str(5000);
756 1         11 my $rsa ='-----BEGIN PRIVATE KEY-----
757             MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqPfgaTEWEP3S9w0t
758             gsicURfo+nLW09/0KfOPinhYZ4ouzU+3xC4pSlEp8Ut9FgL0AgqNslNaK34Kq+NZ
759             jO9DAQIDAQABAkAgkuLEHLaqkWhLgNKagSajeobLS3rPT0Agm0f7k55FXVt743hw
760             Ngkp98bMNrzy9AQ1mJGbQZGrpr4c8ZAx3aRNAiEAoxK/MgGeeLui385KJ7ZOYktj
761             hLBNAB69fKwTZFsUNh0CIQEJQRpFCcydunv2bENcN/oBTRw39E8GNv2pIcNxZkcb
762             NQIgbYSzn3Py6AasNj6nEtCfB+i1p3F35TK/87DlPSrmAgkCIQDJLhFoj1gbwRbH
763             /bDRPrtlRUDDx44wHoEhSDRdy77eiQIgE6z/k6I+ChN1LLttwX0galITxmAYrOBh
764             BVl433tgTTQ=
765             -----END PRIVATE KEY-----';
766 1         5 my $key = '-----BEGIN PRIVATE KEY-----
767             MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYirTZSx+5O8Y6tlG
768             cka6W6btJiocdrdolfcukSoTEk+hRANCAAQkvPNu7Pa1GcsWU4v7ptNfqCJVq8Cx
769             zo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7fJDiFqz3
770             -----END PRIVATE KEY-----';
771 1         9 foreach (1..$iter) {
772 250         671 my $extra = _random_str(100);
773 250         1019 my $data_in = $data.$extra;
774 250         941 my $token = encode_jwt(
775             payload => $data_in,
776             alg => 'ES256',
777             key => \$key,
778             );
779              
780 250         2193096 my $data_out = _decode_jwt2(token=>$token, key=>\$key);
781 250 50       1062 $d->add($token) if $data_in eq $data_out.$extra;
782              
783 250         899 $token = encode_jwt(
784             payload => $data_in,
785             alg => 'RS256',
786             key => \$rsa,
787             );
788              
789 250         212282 $data_out = _decode_jwt2(token=>$token, key=>\$rsa);
790 250 50       1382 $d->add($token) if $data_in eq $data_out.$extra;
791             }
792 1         26 return $d->hexdigest;
793             }
794              
795             sub bench_formattext {
796 1     1 0 6 my $iter = shift;
797 1         17 my $d = Digest->new("MD5");
798 1         66 my $file;
799 1         7 for (0..$iter-1) {
800 4         5628444 my $i = $_ % 2;
801 4         74 $file = catfile($datadir, "wiki$i.html");
802 4         72 my $tree = HTML::TreeBuilder->new->parse_file($file);
803 4         14139341 my $formatter = HTML::FormatText->new();
804 4         228 my $text = $formatter->format($tree);
805 4         10784934 $formatter = HTML::FormatText->new(leftmargin => 0, rightmargin => 30);
806 4         17787 $d->add(Encode::encode_utf8($formatter->format($tree)));
807             }
808 1         5232345 return $d->hexdigest;
809             }
810              
811             sub bench_matrixreal {
812 2     2 0 15 my $iter = shift;
813 2         57 my $d = Digest->new("MD5");
814 2         197 my $smatrix = Math::MatrixReal->new_random(13);
815 2         2755 my $matrix = Math::MatrixReal->new_random(20);
816 2         4223 my $bmatrix = Math::MatrixReal->new_random(72);
817              
818 2         45123 for (1..$iter) {
819 1300         14912 my $r = rand(10);
820 1300         7126 my $m1 = $r*$bmatrix;
821 1300         9327032 my $m2 = $bmatrix*$r;
822 1300         9108865 my $m3 = $bmatrix->multiply_scalar($bmatrix,$r);
823             # Should be zero
824 1300 100       8305345 $d->add($m1->element(1, 1) + $m2->element(1, 1) - 2 * $bmatrix->element(1, 1))
825             if $_ % 10 == 1;
826              
827 1300         15289 my $m = $matrix->det;
828 1300 100       15671784 $d->add($m) if $_ % 10 == 1;
829 1300         4752 $m =$matrix->decompose_LR->det_LR;
830 1300 100       15529575 $d->add($m) if $_ % 10 == 1;
831 1300         8345 $m1 = $matrix ** 2;
832 1300         16329023 $m2 = $matrix * $matrix;
833             #should be zero
834 1300 100       15861379 $d->add($m1->element(1, 1) - $m2->element(1, 1))
835             if $_ % 10 == 1;
836 1300         12193 $m1 = $smatrix->inverse();
837 1300         14412691 $m2 = $smatrix ** -1;
838 1300         14873974 $m3 = $smatrix->decompose_LR->invert_LR;
839 1300 100       14333866 $d->add($m1->element(1, 1), $m2->element(1, 1), $m3->element(1, 1))
840             if $_ % 10 == 1;
841             }
842              
843 2         312 return $d->hexdigest;
844             }
845              
846             sub bench_moose {
847 1     1 0 3 my $iter = shift;
848 1         3 my $cnt = 0;
849              
850 1         5 for (1..$iter) {
851 10000         4381130 my $p = rand(1000);
852 10000         35693 my $root = Benchmark::DKbench::MooseTree->new(node => 'root');
853 10000         11451420 $root->price($p);
854 10000         275816 $root->node;
855 10000         29048 $root->cost;
856 10000         34046 my $lchild = $root->left;
857 10000         293429 $lchild->node('child');
858 10000         280255 $lchild->price($p);
859 10000         275185 $lchild->tax;
860 10000         33407 my $child = $root->right;
861 10000         29952 $child->cost;
862 10000         32562 my $grandchild = $child->left;
863 10000         286302 $grandchild->node('grandchild');
864 10000         314870 $grandchild->has_parent;
865 10000         271249 $grandchild->parent;
866 10000         274312 $grandchild->price($p);
867 10000         27893 $grandchild->cost;
868 10000         36597 my $ggchild = $grandchild->right;
869 10000         33549 $ggchild->cost;
870 10000         41563 $cnt += 5;
871             }
872 1         456 return md5_hex("$cnt objects");
873             }
874              
875             sub bench_moose_prv {
876 2     2 0 11 my $iter = shift;
877 2         47 my $tdir = catfile($datadir, 't');
878 2         19 my $result;
879 2 100       17 if ($iter < 1) {
880 1         8 $tdir = catfile($tdir, 'recipes');
881 1         20135685 $result = `prove -rQ $tdir`;
882             } else {
883 1         78164299 $result = `prove -rQ $tdir` for (1..$iter);
884             }
885 2 50       323 if ($result =~ /Result: (\w*)/) {
886 2         240 return $1;
887             } else {
888 0         0 return '?';
889             }
890             }
891              
892             sub bench_primes_m {
893 1     1 0 37 my $iter = shift;
894 1         76 return bench_primes($iter, 7_500_000);
895             }
896              
897             sub bench_primes {
898 1     1 0 41 my $iter = shift;
899 1         44 my $max = shift;
900 1         35 my @primes;
901 1         87 @primes = _get_primes($max) for (1..$iter);
902 1         10102 return md5_hex(scalar(@primes)." primes up to $max");
903             }
904              
905             sub bench_regex_asc {
906 1     1 0 12 my $iter = shift;
907 1         24 return bench_regex_subst($iter, '');
908             }
909              
910             sub bench_regex_utf8 {
911 1     1 0 19 my $iter = shift;
912 1         47 return bench_regex_subst($iter, 'utf8');
913             }
914              
915             sub bench_regex_subst {
916 2     2 0 24 my $iter = shift;
917 2         21 my $enc = shift;
918 2         32 my $str = _read_wiki_files($enc);
919 2         20 my $match = bench_regex($str, $iter);
920 2         19 my $repl = bench_subst($str, $iter);
921 2         43 return md5_hex($match, $repl);
922             }
923              
924             sub bench_regex {
925 2     2 0 7 my $str = shift;
926 2         11 my $iter = shift;
927 2         8 my $count;
928 2         18 for (1..$iter) {
929 11         96 $count = 0;
930 11         1077698 $count += () = $str =~ /\b[A-Z][a-z]+/g;
931 11         1283209 $count += () = $str =~ /([\w\.+-]+)@[\w\.-]+\.([\w\.-]+)/g;
932 11         933659 $count += () = $str =~ m![\w]+://[^/\s?#]+[^\s?#]+(?:\?[^\s#]*)?(?:#[^\s]*)?!g;
933             }
934 2         73 return "$count Matched";
935             }
936              
937             sub bench_subst {
938 2     2 0 8 my $str = shift;
939 2         20 my $iter = shift;
940 2         8 my $count;
941 2         20 for (1..$iter) {
942 11         185 my $copy = $str;
943 11         35 $count = 0;
944 11         972846 while (my $s = $copy =~ s#<([^>]+)>([^>]*?)</\1>#$2#g) {
945 44         3711458 $count += $s;
946             }
947 11         561406 $copy = substr($copy, int(rand(100))+1) for 1..10;
948             }
949 2         48 return "$count Replaced";
950             }
951              
952             sub bench_textlevenshtein {
953 1     1 0 9 my $iter = shift;
954 1         48 my $d = Digest->new("MD5");
955 1         119 my $data = _fuzzy_data();
956 1         8 my $diff;
957 1         11 foreach (1..$iter) {
958 7         49 foreach my $sz (qw/10 100 1000 2500/) {
959 28         70 my $n = scalar @{$data->{$sz}};
  28         128  
960 28         139 my $i = int(rand($n));
961             $diff = Text::Levenshtein::XS::distance(
962             $data->{$sz}->[$i], $data->{$sz}->[$_]
963 28         177 ) for 0..$n-1;
964 28   100     1774842 $d->add($diff || -1);
965 28 100       143 next if $sz > 1000;
966             $diff = Text::Levenshtein::Damerau::XS::xs_edistance(
967             $data->{$sz}->[$i], $data->{$sz}->[$_]
968 21         137 ) for 0..$n-1;
969 21         1648316 $d->add($diff);
970             }
971             }
972 1         96 return $d->hexdigest;
973             }
974              
975             sub bench_timepiece {
976 1     1 0 14 my $iter = shift;
977 1         23 my $t = Time::Piece::localtime(1692119499);
978 1         202 my $d = Digest->new("MD5");
979 1         51 my $day = 3600*24;
980 1         34 local $ENV{TZ} = 'UTC';
981              
982 1         10 for (1..$iter) {
983 75000         298838 $t += int(rand(1000)-500)*$day;
984 75000 50       2193586 $t += 100000*$day if $t->year < 1970;
985 75000         488812 my $str = $t->strftime("%w, %d %m %Y %H:%M:%S");
986 75000         5242560 eval '$t = Time::Piece->strptime($str, "%w, %d %m %Y %H:%M:%S")';
987 75000         1583931 my $jd = $t->julian_day;
988 75000         5675781 $d->add($str,$jd);
989             }
990 1         20 return $d->hexdigest;
991             }
992              
993             sub total_stats {
994 2     2 0 24 my ($opt, $stats) = @_;
995 2         25 my $benchmarks = benchmark_list();
996 2 100       25 my $display = $opt->{time} ? 'times' : 'scores';
997 2 100       34 my $title = $opt->{time} ? 'Time (sec)' : 'Score';
998 2         23 print "Aggregates:\n".pad_to("Benchmark",24).pad_to("Avg $title").pad_to("Min $title").pad_to("Max $title");
999 2 100       34 print pad_to("Pass %") unless $opt->{time};
1000 2         62 print "\n";
1001 2         76 foreach my $bench (sort keys %$benchmarks) {
1002 42 100       195 next unless $stats->{$bench}->{$display};
1003 2         33 my $str = calc_stats($opt, $stats->{$bench}->{$display});
1004 2         27 print pad_to("$bench:",24).$str;
1005             print pad_to(
1006             sprintf("%d", 100 * ($opt->{iter}-($stats->{$bench}->{fail} || 0)) / $opt->{iter}))
1007 2 100 50     61 unless $opt->{time};
1008 2         42 print "\n";
1009             }
1010 2         44 my $str = calc_stats($opt, $stats->{total}->{$display});
1011 2         34 print pad_to("Overall Avg $title:", 24)."$str\n";
1012             }
1013              
1014             sub calc_stats {
1015 4     4 0 31 my $opt = shift;
1016 4         10 my $arr = shift;
1017 4         19 my $pad = shift;
1018 4         40 my ($min, $max, $avg) = min_max_avg($arr);
1019 4         26 return $avg, join '', map {pad_to(sprintf($opt->{f}, $_), $pad)} ($avg,$min,$max);
  12         110  
1020             }
1021              
1022             sub min_max_avg {
1023 24     24 0 74 my $arr = shift;
1024 24 100       102 return (0, 0, 0) unless @$arr;
1025 23         229 return min(@$arr), max(@$arr), sum(@$arr)/scalar(@$arr);
1026             }
1027              
1028             sub avg_stdev {
1029 8     8 0 28 my $arr = shift;
1030 8 100       55 return (0, 0) unless @$arr;
1031 7         46 my $sum = sum(@$arr);
1032 7         36 my $avg = $sum/scalar(@$arr);
1033 7         24 my @sq;
1034 7         79 push @sq, ($avg - $_)**2 for (@$arr);
1035 7         53 my $dev = min_max_avg(\@sq);
1036 7         58 return $avg, sqrt($dev);
1037             }
1038              
1039             # $single = single tail of dist curve outlier, 1 for over (right), -1 for under (left)
1040             sub drop_outliers {
1041 7     7 0 37 my $arr = shift;
1042 7         29 my $single = shift;
1043 7         58 my ($avg, $stdev) = avg_stdev($arr);
1044 7         24 my @newarr;
1045 7         32 foreach (@$arr) {
1046 43 100       91 if ($single) {
1047 30 100       103 push @newarr, $_ unless $single*($_ - $avg) > 2*$stdev;
1048             } else {
1049 13 100       33 push @newarr, $_ unless abs($avg - $_) > 2*$stdev;
1050             }
1051             }
1052 7         57 return @newarr;
1053             }
1054              
1055             sub pad_to {
1056 115     115 0 353 my $str = shift;
1057 115   100     599 my $len = shift || 20;
1058 115         17015 return $str." "x($len-length($str));
1059             }
1060              
1061             sub _read_wiki_files {
1062 4   100 4   65 my $enc = shift || '';
1063 4         16 my $str = "";
1064 4         28 for (0..2) {
1065 12 50       1162 open my $fh, "<:$enc", catfile($datadir,"wiki$_.html") or die $!;
1066 12         67 $str .= do { local $/; <$fh> };
  12         85  
  12         35484  
1067             }
1068 4         46 return $str;
1069             }
1070              
1071             sub _random_str {
1072 5264   100 5264   17171 my $length = shift || 1;
1073 5264         8263 my $abc = shift;
1074 5264 100       15115 my ($base, $rng) = $abc ? (65, 26) : (32, 95);
1075 5264         10912 my $str = "";
1076 5264         47476 $str .= chr(int(rand($rng))+$base) for 1..$length;
1077 5264         38501 return $str;
1078             }
1079              
1080             sub _random_uchar {
1081 46303     46303   67000 my $chr = int(rand(800))+32;
1082 46303 100       80372 $chr += 128 if $chr > 127; # Skip Latin 1 supplement
1083 46303 100       74992 $chr += 288 if $chr > 591; # Skip pre-Greek blocks
1084 46303         147404 return chr($chr);
1085             }
1086              
1087             sub _fuzzy_data {
1088 1     1   10 my %data;
1089 100         235 push @{$data{10}}, join('', map {_random_uchar()} 1..(8+int(rand(5))))
  996         1359  
1090 1         14 for 0..99;
1091 1         9 push @{$data{100}}, $data{10}->[$_]x10 for 0..49;
  50         193  
1092 1         11 push @{$data{1000}}, _random_str(50,1)x20 for 0..7;
  8         35  
1093 1         6 push @{$data{2500}}, _random_str(50,1)x50 for 0..3;
  4         20  
1094 1         22 return \%data;
1095             }
1096              
1097             sub _rand_where {
1098 16477     16477   25126 my $p = rand();
1099 16477 100       32924 if ($p > 0.5) {
    100          
1100 8377         29919 return {foo => rand(10)};
1101             } elsif ($p > 0.2) {
1102 4922         27709 return {bar => {-in => [int($p*10)..int($p*20)]}};
1103             } else {
1104 3178 100       7011 my $op = $p > 0.1 ? '-and' : '-or';
1105 3178         6457 my @cond = map {_rand_where()} 1..int(rand(3)+1);
  6386         11837  
1106 3178         12903 return {$op => [@cond]};
1107             }
1108             }
1109              
1110             sub _db_data {
1111 1     1   5 my (@data, @cols);
1112 1         6 foreach (1..20) {
1113             my $d = {
1114             id => int(rand(10000000)),
1115             date => \"NOW()",
1116 20         68 map {"data".$_ => "foo bar" x int(rand(5)+1)} 1..int(rand(20)+1)
  202         756  
1117             };
1118 20         70 push @data, $d;
1119 20         183 push @cols, [sort keys %$d];
1120             }
1121 1         14 return \@data, \@cols;
1122             }
1123              
1124             sub compare_obj {
1125 28702     28702 0 44815 my ($obj1, $obj2) = @_;
1126 28702         39885 my $t1 = ref($obj1);
1127 28702         37020 my $t2 = ref($obj2);
1128 28702 100       48896 return 0 if $t1 ne $t2;
1129 28701 100       78833 return $obj1 eq $obj2 unless $t1;
1130 3496 100       8296 return $t1 eq 'ARRAY' ? compare_arr($obj1, $obj2) : compare_hash($obj1, $obj2);
1131             }
1132              
1133             sub compare_arr {
1134 1461     1461 0 2365 my ($arr1, $arr2) = @_;
1135 1461         2173 my $sz = scalar @$arr1;
1136 1461 100       2678 return 0 if $sz != scalar @$arr2;
1137 1460         2994 for (0..$sz-1) {
1138 8156 100       13392 return 0 unless compare_obj($arr1->[$_], $arr2->[$_]);
1139             }
1140 1459         3435 return 1;
1141             }
1142              
1143             sub compare_hash {
1144 2039     2039 0 3889 my ($h1, $h2) = @_;
1145 2039 100       5697 return 0 if scalar keys %$h1 != scalar keys %$h2;
1146 2038         7968 for (keys %$h1) {
1147 19945 100       34440 return 0 unless compare_obj($h1->{$_}, $h2->{$_});
1148             }
1149 2037         13692 return 1;
1150             }
1151              
1152             sub rand_arr {
1153 1468     1468 0 2162 my $sz = shift;
1154 1468         2022 my @arr;
1155 1468         2350 for (1..$sz) {
1156 8203         12397 my $len = int(rand(10)) + 1;
1157 8203 100       15610 my $item = rand(1) < 0.9 ? _random_uchar()x($len*5) : rand(1) < 0.5 ? rand_arr($len) : rand_hash($len);
    100          
1158 8203         15506 push @arr, $item;
1159             }
1160 1468         2545 return \@arr;
1161             }
1162              
1163             sub rand_hash {
1164 2044     2044 0 3089 my $sz = shift;
1165 2044         2863 my %hash;
1166 2044         3480 for (1..$sz) {
1167 20008         31622 my $len = int(rand(10)) + 1;
1168 20008 100       37757 my $item = rand(1) < 0.9 ? _random_uchar()x($len*5) : rand(1) < 0.5 ? rand_arr($len) : rand_hash($len);
    100          
1169 20008         32615 $hash{_random_uchar()x($len*4)} = $item;
1170             }
1171 2044         4107 return \%hash;
1172             }
1173              
1174             # modified from https://github.com/famzah/langs-performance/blob/master/primes.pl
1175             sub _get_primes {
1176 2     2   36 my $n = shift;
1177 2         23 my @s = ();
1178 2         106 for (my $i = 3; $i < $n + 1; $i += 2) {
1179 7499998         12978976 push(@s, $i);
1180             }
1181 2         44 my $mroot = $n**0.5;
1182 2         26 my $half = scalar @s;
1183 2         16 my $i = 0;
1184 2         9 my $m = 3;
1185 2         16 while ($m <= $mroot) {
1186 2736 100       5947 if ($s[$i]) {
1187 796         2941 for (my $j = int(($m * $m - 3) / 2); $j < $half; $j += $m) {
1188 13244400         25501247 $s[$j] = 0;
1189             }
1190             }
1191 2736         4011 $i++;
1192 2736         6131 $m = 2 * $i + 3;
1193             }
1194              
1195 2         653045 return 2, grep($_, @s);
1196             }
1197              
1198             # Fix for Crypt::JWT that was submitted as a patch. Will remove if it is merged.
1199             sub _decode_jwt2 {
1200 501     501   1892 my %args = @_;
1201 501         949 my ($header, $payload);
1202              
1203 501 100       9783 if ($args{token} =~
1204             /^([a-zA-Z0-9_-]+)=*\.([a-zA-Z0-9_-]*)=*\.([a-zA-Z0-9_-]*)=*(?:\.([a-zA-Z0-9_-]+)=*\.([a-zA-Z0-9_-]+)=*)?$/
1205             ) {
1206 500 50       1899 if (length($5)) {
1207             # JWE token (5 segments)
1208 0         0 ($header, $payload) =
1209             Crypt::JWT::_decode_jwe($1, $2, $3, $4, $5, undef, {}, {},
1210             %args);
1211             } else {
1212             # JWS token (3 segments)
1213 500         2307 ($header, $payload) =
1214             Crypt::JWT::_decode_jws($1, $2, $3, {}, %args);
1215             }
1216             }
1217 501 100       2053640 return ($header, $payload) if $args{decode_header};
1218 500         1834 return $payload;
1219             }
1220              
1221             # Helper package for Moose benchmark
1222              
1223             {
1224             package Benchmark::DKbench::MooseTree;
1225              
1226 3     3   1938 use Moose;
  3         1375069  
  3         22  
1227              
1228             has 'price' => (is => 'rw', default => 10);
1229             has 'tax' => (is => 'rw', lazy_build => 1);
1230             has 'node' => (is => 'rw', isa => 'Any');
1231             has 'parent' => (
1232             is => 'rw',
1233             isa => 'Benchmark::DKbench::MooseTree',
1234             predicate => 'has_parent',
1235             weak_ref => 1,
1236             );
1237             has 'left' => (
1238             is => 'rw',
1239             isa => 'Benchmark::DKbench::MooseTree',
1240             predicate => 'has_left',
1241             lazy => 1,
1242             builder => '_build_child_tree',
1243             );
1244             has 'right' => (
1245             is => 'rw',
1246             isa => 'Benchmark::DKbench::MooseTree',
1247             predicate => 'has_right',
1248             lazy => 1,
1249             builder => '_build_child_tree',
1250             );
1251             before 'right', 'left' => sub {
1252             my ($self, $tree) = @_;
1253             $tree->parent($self) if defined $tree;
1254             };
1255              
1256             sub _build_tax {
1257 50000     50000   85740 my $self = shift;
1258 50000         1384908 $self->price * 0.2;
1259             }
1260              
1261             sub _build_child_tree {
1262 40000     40000   64430 my $self = shift;
1263 40000         120478 return Benchmark::DKbench::MooseTree->new( parent => $self );
1264             }
1265              
1266             sub cost {
1267 40000     40000 0 69179 my $self = shift;
1268 40000         1097852 $self->price + $self->tax;
1269             }
1270             }
1271              
1272             1;