File Coverage

blib/lib/Benchmark/DKbench.pm
Criterion Covered Total %
statement 678 714 94.9
branch 164 192 85.4
condition 54 71 76.0
subroutine 88 91 96.7
pod 3 45 6.6
total 987 1113 88.6


line stmt bran cond sub pod time code
1             package Benchmark::DKbench;
2              
3 3     3   587609 use strict;
  3         18  
  3         88  
4 3     3   15 use warnings;
  3         5  
  3         87  
5              
6 3     3   14 use Config;
  3         6  
  3         130  
7 3     3   1599 use Digest;
  3         1705  
  3         98  
8 3     3   20 use Digest::MD5 qw(md5_hex);
  3         6  
  3         162  
9 3     3   633 use Encode;
  3         16139  
  3         244  
10 3     3   1494 use File::Spec::Functions;
  3         2551  
  3         224  
11 3     3   21 use List::Util qw(min max sum);
  3         6  
  3         180  
12 3     3   19 use Time::HiRes qw(CLOCK_MONOTONIC);
  3         5  
  3         26  
13 3     3   2471 use Time::Piece;
  3         33372  
  3         12  
14              
15 3     3   1774 use Astro::Coord::Constellations 'constellation_for_eq';
  3         58301  
  3         197  
16 3     3   24 use Astro::Coord::Precession 'precess';
  3         9  
  3         134  
17 3     3   1948 use Crypt::JWT qw(encode_jwt decode_jwt);
  3         168541  
  3         221  
18 3     3   1590 use CSS::Inliner;
  3         437141  
  3         116  
19 3     3   2720 use DateTime;
  3         1609876  
  3         167  
20 3     3   4980 use DBI;
  3         55353  
  3         219  
21 3     3   29 use File::ShareDir 'dist_dir';
  3         6  
  3         157  
22 3     3   1540 use HTML::FormatText;
  3         21817  
  3         98  
23 3     3   22 use HTML::TreeBuilder;
  3         32  
  3         35  
24 3     3   2763 use Imager;
  3         153666  
  3         35  
25 3     3   1524 use Imager::Filter::Mandelbrot;
  3         1828  
  3         104  
26 3     3   1688 use Image::PHash;
  3         19082  
  3         152  
27 3     3   21 use JSON::XS;
  3         9  
  3         197  
28 3     3   18 use Math::DCT ':all';
  3         6  
  3         421  
29 3     3   3255 use Math::MatrixReal;
  3         89279  
  3         219  
30 3     3   1758 use MCE::Loop;
  3         151230  
  3         54  
31 3     3   3419 use SQL::Abstract::Classic;
  3         36309  
  3         109  
32 3     3   1544 use SQL::Inserter;
  3         6470  
  3         147  
33 3     3   1453 use System::CPU;
  3         8374  
  3         110  
34 3     3   1376 use System::Info;
  3         56323  
  3         226  
35 3     3   1432 use Text::Levenshtein::Damerau::XS;
  3         1881  
  3         152  
36 3     3   1307 use Text::Levenshtein::XS;
  3         1841  
  3         156  
37              
38 3     3   20 use Exporter 'import';
  3         7  
  3         27509  
39             our @EXPORT = qw(system_identity suite_run calc_scalability);
40             our $datadir = dist_dir("Benchmark-DKbench");
41             my $mono_clock = $^O !~ /win/i || $Time::HiRes::VERSION >= 1.9764;
42              
43             our $VERSION = '2.4';
44              
45             =head1 NAME
46              
47             Benchmark::DKbench - Perl CPU Benchmark
48              
49             =head1 SYNOPSIS
50              
51             # Run the suite single-threaded and then multi-threaded on multi-core systems
52             # Will print scores for the two runs and multi/single thread scalability
53             dkbench
54              
55             # A dual-thread "quick" run (with times instead of scores)
56             dkbench -j 2 -q
57              
58             # If BioPerl is installed, enable the BioPerl benchmarks by downloading Genbank data
59             dkbench --setup
60              
61             # Force install the reference versions of all CPAN modules
62             setup_dkbench --force
63              
64             =head1 DESCRIPTION
65              
66             A Perl benchmark suite for general compute, created to evaluate the comparative
67             performance of systems when running computationally intensive Perl (both pure Perl
68             and C/XS) workloads. It is a good overall indicator for generic CPU performance in
69             real-world scenarios. It runs single and multi-threaded (able to scale to hundreds
70             of CPUs) and can be fully customized to run the benchmarks that better suit your own
71             scenario.
72              
73             =head1 INSTALLATION
74              
75             See the L</"setup_dkbench"> script below for more on the installation of a couple
76             of optional benchmarks and standardizing your benchmarking environment, otherwise
77             here are some general guidelines for verious systems.
78              
79             =head2 Linux / WSL etc
80              
81             The only non-CPAN software required to install/run the suite is a build environment
82             for the C/XS modules (C compiler, make etc.) and Perl. On the most popular Linux
83             package managers you can easily set up such an environment (as root or with sudo):
84              
85             # Debian/Ubuntu etc
86             apt-get update
87             apt-get install build-essential perl cpanminus
88              
89             # CentOS/Red Hat
90             yum update
91             yum install gcc make patch perl perl-App-cpanminus
92              
93             After that, you can use L<App::cpanminus> to install the benchmark suite (as
94             root/sudo is the easiest, will install for all users):
95              
96             cpanm -n Benchmark::DKbench
97              
98             =head2 Solaris
99              
100             You will need to install the Oracle Solaris Studio development package to have a
101             compiler environment, and to add its C<bin> directory to your PATH, before installing
102             the benchmark suite.
103              
104             =head2 Strawberry Perl
105              
106             If you are on Windows, you should be using the Windows Subsystem for Linux (WSL)
107             for running Perl or, if you can't (e.g. old Windows verions), cygwin instead.
108             The suite should still work on Strawberry Perl, as long as you don't try to run
109             tests when installing (some dependencies will not pass them). The simplest way is
110             with L<App::cpanminus> (most Strawberry Perl verions have it installed):
111              
112             cpanm -n Benchmark::DKbench
113              
114             otherwise with the base CPAN shell:
115              
116             perl -MCPAN -e shell
117              
118             > notest install Benchmark::DKbench
119              
120             and then note that the scripts get the batch extension appended, so C<dkbench.bat>
121             runs the suite (and C<setup_dkbench.bat> can assist with module versions, optional
122             benchmarks etc.).
123              
124             Be aware that Strawberry Perl is slower, on my test system I get almost 50% slower
125             performance than WSL and 30% slower than cygwin.
126              
127             =head1 SCRIPTS
128              
129             You will most likely only ever need the main script C<dkbench> which launches the
130             suite, although C<setup_dkbench> can help with setup or standardizing/normalizing your
131             benchmarking environment.
132              
133             =head2 C<dkbench>
134              
135             The main script that runs the DKbench benchmark suite. If L<BioPerl> is installed,
136             you may want to start with C<dkbench --setup>. But beyond that, there are many
137             options to control number of threads, iterations, which benchmarks to run etc:
138              
139             dkbench [options]
140              
141             Options:
142             --threads <i>, -j <i> : Number of benchmark threads (default is 1).
143             --multi, -m : Multi-threaded using all your CPU cores/threads.
144             --max_threads <i> : Override the cpu detection to specify max cpu threads.
145             --iter <i>, -i <i> : Number of suite iterations (with min/max/avg at the end).
146             --stdev : Show relative standard deviation (for iter > 1).
147             --include <regex> : Run only benchmarks that match regex.
148             --exclude <regex> : Do not run benchmarks that match regex.
149             --time, -t : Report time (sec) instead of score.
150             --quick, -q : Quick benchmark run (implies -t).
151             --no_mce : Do not run under MCE::Loop (implies -j 1).
152             --scale <i>, -s <i> : Scale the bench workload by x times (incompatible with -q).
153             --skip_bio : Skip BioPerl benchmarks.
154             --skip_prove : Skip Moose prove benchmark.
155             --time_piece : Run optional Time::Piece benchmark (see benchmark details).
156             --bio_codons : Run optional BioPerl Codons benchmark (does not scale well).
157             --sleep <i> : Sleep for <i> secs after each benchmark.
158             --setup : Download the Genbank data to enable the BioPerl tests.
159             --datapath <path> : Override the path where the expected benchmark data is found.
160             --ver <num> : Skip benchmarks added after the specified version.
161             --help -h : Show basic help and exit.
162              
163             The default run (no options) will run all the benchmarks both single-threaded and
164             multi-threaded (using all detected CPU cores/hyperthreads) and show you scores and
165             multi vs single threaded scalability.
166              
167             The scores are calibrated such that a reference CPU (Intel Xeon Platinum 8481C -
168             Sapphire Rapids) would achieve a score of 1000 in a single-core benchmark run using
169             the default software configuration (Linux/Perl 5.36.0 built with multiplicity and
170             threads, with reference CPAN module versions). Perl built without thread support and
171             multi(plicity) will be a bit faster (usually in the order of ~3-4%), while older Perl
172             versions will most likely be slower. Different CPAN module versions will also impact
173             scores, using C<setup_dkbench> is a way to ensure a reference environment for more
174             meaningful hardware comparisons.
175              
176             The multi-thread scalability calculated by the suite should approach 100% if each
177             thread runs on a full core (i.e. no SMT), and the core can maintain the clock speed
178             it had on the single-thread runs. Note that the overall scalability is an average
179             of the benchmarks that drops non-scaling outliers (over 2*stdev less than the mean).
180              
181             If you want to reduce the effects of thermal throttling, which will lower the speed
182             of (mainly multi-threaded) benchmarks as the CPU temperature increases, the C<sleep>
183             option can help by adding cooldown time between each benchmark.
184              
185             The suite will report a Pass/Fail per benchmark. A failure may be caused if you have
186             different CPAN module version installed - this is normal, and you will be warned.
187              
188             L<MCE::Loop> is used to run on the desired number of parallel threads, with minimal
189             overhead., There is an option to disable it, which forces a single-thread run.
190              
191             =head2 C<setup_dkbench>
192              
193             Simple installer to check/get the reference versions of CPAN modules and download
194             the Genbank data file required for the BioPerl benchmarks of the DKbench suite.
195              
196             It assumes that you have some software already installed (see L</"INSTALLATION"> above),
197             try C<setup_dkbench --help> will give you more details.
198              
199             setup_dkbench [--force --sudo --test --data=s --help]
200              
201             Options:
202             --sudo : Will use sudo for cpanm calls.
203             --force : Will install reference CPAN module versions and re-download the genbank data.
204             --test : Will run the test suites for the CPAN module (default behaviour is to skip).
205             --data=s : Data dir path to copy files from. Should not need if you installed DKbench.
206             --help : Print this help text and exit.
207              
208             Running it without any options will fetch the data for the BioPerl tests (similar to
209             C<dkbench --setup>) and use C<cpanm> to install any missing libraries.
210              
211             Using it with C<--force> will install the reference CPAN module versions, including
212             BioPerl which is not a requirement for DKbench, but enables the BioPerl benchmarks.
213              
214             The reference Perl and CPAN versions are suggested if you want a fair comparison
215             between systems and also for the benchmark Pass/Fail results to be reliable.
216              
217             =head1 BENCHMARKS
218              
219             The suite consists of 21 benchmarks, 19 will run by default. However, the
220             C<BioPerl Monomers> requires the optional L<BioPerl> to be installed and Genbank
221             data to be downloaded (C<dkbench --setup> can do the latter), so you will only
222             see 18 benchmarks running just after a standard install. Because the overall score
223             is an average, it is generally unaffected by adding or skipping a benchmark or two.
224              
225             The optional benchmarks are enabled with the C<--time_piece> and C<--bio_codons>
226             options.
227              
228             =over 4
229              
230             =item * C<Astro> : Calculates precession between random epochs and finds the
231             constellation for random equatorial coordinates using L<Astro::Coord::Precession>
232             and L<Astro::Coord::Constellations> respectively.
233              
234             =item * C<BioPerl Codons> : Counts codons on a sample bacterial sequence. Requires
235             L<BioPerl> to be installed.
236             This test does not scale well on multiple threads, so is disabled by default (use
237             C<--bio_codons>) option. Requires data fetched using the C<--setup> option.
238              
239             =item * C<BioPerl Monomers> : Counts monomers on 500 sample bacterial sequences using
240             L<BioPerl> (which needs to be installed). Requires data fetched using the C<--setup>
241             option.
242              
243             =item * C<CSS::Inliner> : Inlines CSS on 2 sample wiki pages using L<CSS::Inliner>.
244              
245             =item * C<Crypt::JWT> : Creates large JSON Web Tokens with RSA and EC crypto keys
246             using L<Crypt::JWT>.
247              
248             =item * C<DateTime> : Creates and manipulates L<DateTime> objects.
249              
250             =item * C<DBI/SQL> : Creates a mock L<DBI> connection (using L<DBD::Mock>) and passes
251             it insert/select statements using L<SQL::Inserter> and L<SQL::Abstract::Classic>.
252             The latter is quite slow at creating the statements, but it is widely used.
253              
254             =item * C<Digest> : Creates MD5, SH1 and SHA-512 digests of a large string.
255              
256             =item * C<Encode> : Encodes/decodes large strings from/to UTF-8/16, cp-1252.
257              
258             =item * C<HTML::FormatText> : Converts HTML to text for 2 sample wiki pages using
259             L<HTML::FormatText>.
260              
261             =item * C<Imager> : Loads a sample image and performs edits/manipulations with
262             L<Imager>, including filters like gaussian, unsharp mask, mandelbrot.
263              
264             =item * C<JSON::XS> : Encodes/decodes random data structures to/from JSON using
265             L<JSON::XS>.
266              
267             =item * C<Math::DCT> : Does 8x8, 18x18 and 32x32 DCT transforms with L<Math::DCT>.
268              
269             =item * C<Math::MatrixReal> : Performs various manipulations on L<Math::MatrixReal>
270             matrices.
271              
272             =item * C<Moose> : Creates L<Moose> objects.
273              
274             =item * C<Moose prove> : Runs 110 tests from the Moose 2.2201 test suite. The least
275             CPU-intensive test (which is why there is the option C<--no_prove> to disable it),
276             most of the time will be spent loading the interpreter and the Moose module for each
277             test, which is behaviour representative of how a Perl test suite runs by default.
278              
279             =item * C<Primes> : Calculates all primes up to 7.5 million. Small number with
280             repeat was chosen to keep low memory (this is a pure Perl function no Math libraries).
281              
282             =item * C<Regex/Subst> : Concatenates 3 wiki pages into a byte string then matches
283             3 typical regexes (for names, emails, URIs), replaces html tags with their contents
284             (starting with the innermost) and does calls subst a few times.
285              
286             =item * C<Regex/Subst utf8> : Exactly the same as C<Regex/Subst>, but reads into
287             a utf8 string. Perl version can make a big difference, as Unicode behaviour has
288             changed (old Perl versions are faster but less strict in general).
289              
290             =item * C<Text::Levenshtein> : The edit distance for strings of various lengths (up
291             to 2500) are calculated using L<Text::Levenshtein::XS> and L<Text::Levenshtein::Damerau::XS>.
292              
293             =item * C<Time::Piece> : Creates and manipulates/converts Time::Piece objects. It
294             is disabled by default because it uses the OS time libraries, so it might skew results
295             if you are trying to compare CPUs on different OS platforms. It can be enabled with
296             the C<--time_piece> option. For MacOS specifically, it can only be enabled if C<--no_mce>
297             is specified, as it runs extremely slow when forked.
298              
299             =back
300              
301             =head1 EXPORTED FUNCTIONS
302              
303             You will normally not use the Benchmark::DKbench module itself, but here are the
304             exported functions that the C<dkbench> script uses for reference:
305              
306             =head2 C<system_identity>
307              
308             my $cores = system_identity();
309              
310             Prints out software/hardware configuration and returns then number of cores detected.
311              
312             =head2 C<suite_run>
313              
314             my %stats = suite_run(\%options);
315              
316             Runs the benchmark suite given the C<%options> and prints results. Returns a hash
317             with run stats.
318              
319             The options accepted are the same as the C<dkbench> script (in their long form),
320             except C<help>, C<setup> and C<max_threads> which are command-line only.
321              
322             =head2 C<calc_scalability>
323              
324             calc_scalability(\%options, \%stat_single, \%stat_multi);
325              
326             Given the C<%stat_single> results of a single-threaded C<suite_run> and C<%stat_multi>
327             results of a multi-threaded run, will calculate and print the multi-thread scalability.
328              
329             =head1 NOTES
330              
331             The benchmark suite was created to compare the performance of various cloud offerings.
332             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>
333             as well as the L<2023 follow-up|https://dev.to/dkechag/cloud-vm-performance-value-comparison-2023-perl-more-1kpp>.
334              
335             The benchmarks for the first version were more tuned to what I would expect to run
336             on the servers I was testing, in order to choose the optimal types for the company
337             I was working for. The second version has expanded a bit over that, and is friendlier
338             to use.
339              
340             Althought this benchmark is in general a good indicator of general CPU performance
341             and can be customized to your needs, no benchmark is as good as running your own
342             actual workload.
343              
344             =head2 SCORES
345              
346             Some sample DKbench score results from various systems for comparison (all on
347             reference setup with Perl 5.36.0 thread-multi):
348              
349             CPU Cores/HT Single Multi Scalability
350             Intel i7-4750HQ @ 2.0 (MacOS) 4/8 612 2332 46.9%
351             AMD Ryzen 5 PRO 4650U @ 2.1 (WSL) 6/12 905 4444 40.6%
352             Apple M1 Pro @ 3.2 (MacOS) 10/10 1283 10026 78.8%
353             Apple M2 Pro @ 3.5 (MacOS) 12/12 1415 12394 73.1%
354             Ampere Altra @ 3.0 (Linux) 48/48 708 32718 97.7%
355             Intel Xeon Platinum 8481C @ 2.7 (Linux) 88/176 1000 86055 48.9%
356             AMD EPYC Milan 7B13 @ 2.45 (Linux) 112/224 956 104536 49.3%
357             AMD EPYC Genoa 9B14 @ 2.7 (Linux) 180/360 1197 221622 51.4%
358              
359             =head1 AUTHOR
360              
361             Dimitrios Kechagias, C<< <dkechag at cpan.org> >>
362              
363             =head1 BUGS
364              
365             Please report any bugs or feature requests either on L<GitHub|https://github.com/dkechag/Benchmark-DKbench> (preferred), or on RT (via the email
366             C<bug-Benchmark-DKbench at rt.cpan.org> or L<web interface|https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Benchmark-DKbench>).
367              
368             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
369              
370             =head1 GIT
371              
372             L<https://github.com/dkechag/Benchmark-DKbench>
373              
374             =head1 LICENSE AND COPYRIGHT
375              
376             This software is copyright (c) 2021-2023 by Dimitrios Kechagias.
377              
378             This is free software; you can redistribute it and/or modify it under
379             the same terms as the Perl 5 programming language system itself.
380              
381             =cut
382              
383             sub benchmark_list {
384             return { # idx : 0 = result, 1 = ref time, 2 = func, 3 = quick test, 4 = normal test, 5 = ver
385 12     12 0 570 'Astro' => ['e71c7ae08f16fe26aea7cfdb72785873', 5.674, \&bench_astro, 20000, 80000],
386             'BioPerl Codons' => ['97c443c099886ca60e99f7ab9df689b5', 8.752, \&bench_bioperl_codons, 3, 5, 1],
387             'BioPerl Monomers' => ['d29ed0a5c205c803c112be1338d1f060', 5.241, \&bench_bioperl_mono, 6, 20],
388             'Crypt::JWT' => ['d41d8cd98f00b204e9800998ecf8427e', 6.451, \&bench_jwt, 250, 900],
389             'CSS::Inliner' => ['82c1b6de9ca0500a48f8a8df0998df3c', 4.603, \&bench_css, 2, 5],
390             'DBI/SQL' => ['2b8252daad9568a5b39038c696df4be3', 5.700, \&bench_dbi, 5000, 15000, 2.1],
391             'DateTime' => ['b08d2eeb994083b7422f6c9d86fed2c6', 6.198, \&bench_datetime, 5000, 15000],
392             'Digest' => ['4b69f6cf0f53cbf6c3444f2f767dd21d', 4.513, \&bench_digest, 50, 250],
393             'Encode' => ['PASS 1025', 5.725, \&bench_encode, 40, 120],
394             'HTML::FormatText' => ['8c2589f0a5276252805e11301fc2ab56', 4.756, \&bench_formattext, 4, 10],
395             'Imager' => ['8829cb3703e884054eb025496f336c63', 6.792, \&bench_imager, 4, 16],
396             'JSON::XS' => ['PASS', 5.388, \&bench_json, 600, 2200],
397             'Math::DCT' => ['766e3bfd7a2276f452bb3d1bd21939bc', 7.147, \&bench_dct, 25000, 100_000],
398             'Math::MatrixReal' => ['4606231b1309fb21ae1223fa0043fd76', 4.293, \&bench_matrixreal, 200, 650],
399             'Moose' => ['d1cb92c513f6378506dfa11f694cffac', 4.968, \&bench_moose, 10_000, 30_000],
400             'Moose prove' => ['PASS', 7.974, \&bench_moose_prv, 0.5, 1],
401             'Primes' => ['4266f70a7a9efb3484cf5d98eba32244', 3.680, \&bench_primes_m, 2, 5],
402             'Regex/Subst' => ['30ce365b25f3d597578b3bdb14aa3f57', 4.652, \&bench_regex_asc, 8, 24],
403             'Regex/Subst utf8' => ['857eb4e63a4d174ca4a16fe678f7626f', 5.703, \&bench_regex_utf8, 3, 10],
404             'Text::Levenshtein' => ['2948a300ed9131fa0ce82bb5eabb8ded', 5.539, \&bench_textlevenshtein, 7, 25, 2.1],
405             'Time::Piece' => ['2d4b149fe7f873a27109fc376d69211b', 5.907, \&bench_timepiece, 75_000, 275_000],
406             };
407             }
408              
409             sub system_identity {
410 1     1 1 1401 my ($physical, $cores, $ncpu) = System::CPU::get_cpu;
411 1   50     2304 $ncpu ||= 1;
412 1 50       5 local $^O = 'linux' if $^O =~ /android/;
413 1         10 my $info = System::Info->sysinfo_hash;
414 1   0     15575 my $osn = $info->{distro} || $info->{os} || $^O;
415 1   50     13 my $model = System::CPU::get_name || '';
416 1   50     2275 my $arch = System::CPU::get_arch || '';
417 1 50       6560 $arch = " ($arch)" if $arch;
418 1         28 print "--------------- Software ---------------\nDKbench v$VERSION\n";
419             printf "Perl $^V (%sthreads, %smulti)\n",
420             $Config{usethreads} ? '' : 'no ',
421 1 50       301 $Config{usemultiplicity} ? '' : 'no ',;
    50          
422 1         16 print "OS: $osn\n--------------- Hardware ---------------\n";
423 1         14 print "CPU type: $model$arch\n";
424 1         9 print "CPUs: $ncpu";
425 1         4 my @extra;
426 1 50 33     36 push @extra, "$physical Processors" if $physical && $physical > 1;
427 1 50       25 push @extra, "$cores Cores" if $cores;
428 1 50 33     17 push @extra, "$ncpu Threads" if $cores && $cores != $ncpu;
429 1 50       17 print " (".join(', ', @extra).")" if @extra;
430 1         21 print "\n".("-"x40)."\n";
431              
432 1         55 return $ncpu;
433             };
434              
435             sub suite_run {
436 6     6 1 33864 my $opt = shift;
437 6 100       45 $datadir = $opt->{datapath} if $opt->{datapath};
438 6   100     104 $opt->{threads} //= 1;
439 6   100     58 $opt->{scale} //= 1;
440 6 100       40 $opt->{f} = $opt->{time} ? '%.3f' : '%5.0f';
441 6         35 my %stats = (threads => $opt->{threads});
442              
443             MCE::Loop::init {
444             max_workers => $opt->{threads},
445             chunk_size => 1,
446 6 100       55 } unless $opt->{no_mce};
447              
448 6         91 foreach (1..$opt->{iter}) {
449 8 100       188 print "Iteration $_ of $opt->{iter}...\n" if $opt->{iter} > 1;
450 8         56 run_iteration($opt, \%stats);
451             }
452              
453 5 100       44 total_stats($opt, \%stats) if $opt->{iter} > 1;
454              
455 5         760 return %stats;
456             }
457              
458             sub calc_scalability {
459 2     2 1 9920 my ($opt, $stats1, $stats2) = @_;
460 2         36 my $benchmarks = benchmark_list();
461 2         18 my $threads = $stats2->{threads}/$stats1->{threads};
462 2 100       32 my $display = $opt->{time} ? 'times' : 'scores';
463 2 100       32 $opt->{f} = $opt->{time} ? '%.3f' : '%5.0f';
464 2         19 my (@perf, @scal);
465 2         25 print "Multi thread Scalability:\n".pad_to("Benchmark",24).pad_to("Multi perf xSingle",24).pad_to("Multi scalability %",24);
466 2         131 print "\n";
467 2         21 my $cnt;
468 2         53 foreach my $bench (sort keys %$benchmarks) {
469 42 100 100     277 next unless $stats1->{$bench}->{times} && $stats2->{$bench}->{times};
470 2         16 $cnt++;
471 2         18 my @res1 = min_max_avg($stats1->{$bench}->{times});
472 2         21 my @res2 = min_max_avg($stats2->{$bench}->{times});
473 2 50       26 push @perf, $res1[2]/$res2[2]*$threads if $res2[2];
474 2 50       30 push @scal, $res1[2]/$res2[2]*100 if $res2[2];
475 2         29 print pad_to("$bench:",24).pad_to(sprintf("%.2f",$perf[-1]),24).pad_to(sprintf("%2.0f",$scal[-1]),24)."\n";
476             }
477 2         130 print (("-"x40)."\n");
478 2         56 my $avg1 = min_max_avg($stats1->{total}->{$display});
479 2         21 my $avg2 = min_max_avg($stats2->{total}->{$display});
480 2         79 print "DKbench summary ($cnt benchmark";
481 2 50       25 print "s" if $cnt > 1;
482 2 50 33     28 print " x$opt->{scale} scale" if $opt->{scale} && $opt->{scale} > 1;
483 2 50 66     39 print ", $opt->{iter} iterations" if $opt->{iter} && $opt->{iter} > 1;
484 2         51 print ", $stats2->{threads} thread";
485 2 50       94 print "s" if $stats2->{threads} > 1;
486 2         103 print "):\n";
487 2 100       22 $opt->{f} .= "s" if $opt->{time};
488 2         18 print pad_to("Single:").sprintf($opt->{f}, $avg1)."\n";
489 2         25 print pad_to("Multi:").sprintf($opt->{f}, $avg2)."\n";
490 2         26 my @newperf = Benchmark::DKbench::drop_outliers(\@perf, -1);
491 2         58 my @newscal = Benchmark::DKbench::drop_outliers(\@scal, -1);
492 2         25 @perf = min_max_avg(\@newperf);
493 2         19 @scal = min_max_avg(\@newscal);
494 2         24 print pad_to("Multi/Single perf:").sprintf("%.2fx\t(%.2f - %.2f)", $perf[2], $perf[0], $perf[1])."\n";
495 2         27 print pad_to("Multi scalability:").sprintf("%2.1f%% \t(%.0f%% - %.0f%%)", $scal[2], $scal[0], $scal[1])."\n";
496             }
497              
498             sub run_iteration {
499 8     8 0 42 my ($opt, $stats) = @_;
500 8         79 my $benchmarks = benchmark_list();
501 8 100       61 my $title = $opt->{time} ? 'Time (sec)' : 'Score';
502 8         39 print pad_to("Benchmark").pad_to($title);
503 8 100       105 print "Pass/Fail" unless $opt->{time};
504 8         91 print "\n";
505 8         44 my ($total_score, $total_time, $i) = (0, 0, 0);
506 8         139 foreach my $bench (sort keys %$benchmarks) {
507 168 100 100     663 next if $opt->{skip_bio} && $bench =~ /Monomers/;
508 166 100 100     533 next if $opt->{skip_prove} && $bench =~ /prove/;
509 164 100 100     676 next if !$opt->{bio_codons} && $bench =~ /Codons/;
510 158 100 100     536 next if !$opt->{time_piece} && $bench =~ /Time::Piece/;
511 152 100 100     502 next if $opt->{ver} && $benchmarks->{$bench}->[5] && $opt->{ver} < $benchmarks->{$bench}->[5];
      100        
512 150 100 100     618 next if $opt->{exclude} && $bench =~ /$opt->{exclude}/;
513 144 100 100     792 next if $opt->{include} && $bench !~ /$opt->{include}/;
514 23 50       146 if ($bench =~ /Bio/) {
515 0         0 require Bio::SeqIO;
516 0         0 require Bio::Tools::SeqStats;
517             }
518 23         94 my ($time, $res) = mce_bench_run($opt, $benchmarks->{$bench});
519 23   50     405 my $score = int(1000*$opt->{threads}*$benchmarks->{$bench}->[1]/($time || 1)+0.5);
520 23         98 $total_score += $score;
521 23         73 $total_time += $time;
522 23         68 $i++;
523 23         57 push @{$stats->{$bench}->{times}}, $time;
  23         352  
524 23         77 push @{$stats->{$bench}->{scores}}, $score;
  23         164  
525 23 100       191 my $d = $stats->{$bench}->{$opt->{time} ? 'times' : 'scores'}->[-1];
526 23 100       180 $stats->{$bench}->{fail}++ if $res ne 'Pass';
527 23         212 print pad_to("$bench:").pad_to(sprintf($opt->{f}, $d));
528 23 100       250 print "$res" unless $opt->{time};
529 23         351 print "\n";
530 23 50       198 sleep $opt->{sleep} if $opt->{sleep};
531             }
532 8 100       254 die "No tests to run\n" unless $i;
533 7         44 my $s = int($total_score/$i+0.5);
534 7 100       62 print pad_to("Overall $title: ").sprintf($opt->{f}."\n", $opt->{time} ? $total_time : $s);
535 7         39 push @{$stats->{total}->{times}}, $total_time;
  7         97  
536 7         21 push @{$stats->{total}->{scores}}, $s;
  7         766  
537             }
538              
539             sub mce_bench_run {
540 23     23 0 73 my $opt = shift;
541 23         48 my $benchmark = shift;
542 23 100       129 $benchmark->[3] = $benchmark->[4] unless $opt->{quick};
543 23 100       124 return bench_run($benchmark) if $opt->{no_mce};
544              
545             my @stats = mce_loop {
546 0     0   0 my ($mce, $chunk_ref, $chunk_id) = @_;
547 0         0 for (@{$chunk_ref}) {
  0         0  
548 0         0 my ($time, $res) = bench_run($benchmark);
549 0         0 MCE->gather([$time, $res]);
550             }
551             }
552 1         34 (1 .. $opt->{threads} * $opt->{scale});
553              
554 1         21341410 my ($res, $time) = ('Pass', 0);
555 1         16 foreach (@stats) {
556 2         22 $time += $_->[0];
557 2 50       29 $res = $_->[1] if $_->[1] ne 'Pass';
558             }
559              
560 1   50     40 return $time/($opt->{threads}*$opt->{scale} || 1), $res;
561             }
562              
563             sub bench_run {
564 23     23 0 224 my ($benchmark, $srand) = @_;
565 23   100     158 $srand //= 1;
566 23         86 srand($srand); # For repeatability
567 23         97 my $t0 = _get_time();
568 23         257 my $out = $benchmark->[2]->($benchmark->[3]);
569 22         40045 my $time = sprintf("%.3f", _get_time()-$t0);
570 22 100       933 my $r = $out eq $benchmark->[0] ? 'Pass' : "Fail ($out)";
571 22         251 return $time, $r;
572             }
573              
574             sub bench_astro {
575 1     1 0 2 my $iter = shift;
576 1         15 my $d = Digest->new("MD5");
577             my $precessed = precess([rand(24), rand(180)-90], rand(200)+1900, rand(200)+1900)
578 1         61 for (1..$iter*10);
579 1         10233624 my $constellation_abbrev;
580             $d->add(constellation_for_eq(rand(24), rand(180)-90, rand(200)+1900))
581 1         21 for (1..$iter);
582 1         7975509 return $d->hexdigest;
583             }
584              
585             sub bench_bioperl_codons {
586 0     0 0 0 my $skip = shift;
587 0         0 my $iter = shift;
588 0         0 my $d = Digest->new("MD5");
589 0         0 my $file = catfile($datadir, "gbbct5.seq");
590 0         0 foreach (1..$iter) {
591 0         0 my $in = Bio::SeqIO->new(-file => $file, -format => "genbank");
592 0         0 $in->next_seq for (1..$skip);
593 0         0 my $seq = $in->next_seq;
594 0         0 my $seq_stats = Bio::Tools::SeqStats->new($seq);
595 0         0 my $codon_ref = $seq_stats->count_codons();
596 0         0 $d->add($_, $codon_ref->{$_}) for sort keys %$codon_ref;
597             }
598 0         0 return $d->hexdigest;
599             }
600              
601             sub bench_bioperl_mono {
602 0     0 0 0 my $iter = shift;
603 0         0 my $file = catfile($datadir, "gbbct5.seq");
604 0         0 my $in = Bio::SeqIO->new(-file => $file, -format => "genbank");
605 0         0 my $d = Digest->new("MD5");
606 0         0 my $builder = $in->sequence_builder();
607 0         0 $builder->want_none();
608 0         0 $builder->add_wanted_slot('display_id','seq');
609 0         0 for (1..$iter) {
610 0         0 my $seq = $in->next_seq;
611 0         0 my $seq_stats = Bio::Tools::SeqStats->new($seq);
612 0         0 my $weight = $seq_stats->get_mol_wt();
613 0         0 $d->add(int($weight->[0]));
614 0         0 my $monomer_ref = $seq_stats->count_monomers();
615 0         0 $d->add($_, $monomer_ref->{$_}) for sort keys %$monomer_ref;
616             }
617 0         0 return $d->hexdigest;
618             }
619              
620             sub bench_css {
621 1     1 0 4 my $iter = shift;
622 1         8 my $d = Digest->new("MD5");
623 1         54 my $file;
624             my $html;
625 1         5 for (1..$iter) {
626 2         3318 my $inliner = new CSS::Inliner();
627 2         1979 my $i = $_ % 2 + 1;
628 2         24 $file = catfile($datadir, "wiki$i.html");
629 2         28 $inliner->read_file({ filename => $file });
630 2         14781962 $html = $inliner->inlinify();
631 2         16065715 $d->add(Encode::encode_utf8($html));
632             }
633 1         28017 return $d->hexdigest;
634             }
635              
636             sub bench_datetime {
637 1     1 0 14 my $iter = shift;
638 1         1351 my @tz = map {DateTime::TimeZone->new( name => $_ )} qw(UTC Europe/London America/New_York);
  3         19488  
639 1         4250 my $d = Digest->new("MD5");
640 1         38 my $str;
641              
642 1         5 for (1..$iter) {
643 5000         124495 my $dt = DateTime->now();
644 5000         1316471 my $dt1 = DateTime->from_epoch(
645             epoch => 946684800 + rand(100000000),
646             );
647 5000         1344484 my $dt2 = DateTime->from_epoch(
648             epoch => 946684800 + rand(100000000),
649             );
650 5000         1317818 $str = $dt2->strftime('%FT%T')."\n";
651 5000         548143 $d->add($str);
652 5000         8620 eval {$dt2->set_time_zone($tz[int(rand(3))])};
  5000         20376  
653 5000         1071767 my $dur = $dt2->subtract_datetime($dt1);
654 5000         6037743 eval {$dt2->add_duration($dur)};
  5000         15522  
655 5000         6008393 eval {$dt2->subtract(days => int(rand(1000)+1))};
  5000         22180  
656 5000         6519491 $dt->week;
657 5000         124908 $dt->epoch;
658 5000         38323 $d->add($dt2->strftime('%FT%T'));
659 5000         572509 eval {$dt2->set( year => int(rand(2030)))};
  5000         18363  
660 5000         3225837 $d->add($dt2->ymd('/'));
661             }
662 1         36 return $d->hexdigest;
663             }
664              
665             sub bench_dbi {
666 1     1 0 4 my $iter = shift;
667 1         11 my $d = Digest->new("MD5");
668 1         96 my $dbh = DBI->connect( 'DBI:Mock:', '', '' );
669 1         17107 my ($data, $cols) = _db_data();
670              
671 1         7 foreach (1..$iter) {
672 5000         2273617 my $inserter = SQL::Inserter->new(
673             dbh => $dbh,
674             table => 'table',
675             cols => $cols,
676             buffer => 2
677             );
678 5000         162005 $inserter->insert($data->[int(rand(20))]) for 1..2;
679 5000         3687674 $d->add($dbh->last_insert_id);
680 5000         55101 my $sql = SQL::Abstract::Classic->new();
681 5000         229956 my ($stmt, @bind) = $sql->insert('table', $data->[int(rand(20))]);
682 5000         3881232 $d->add($dbh->quote($stmt));
683 5000         88306 ($stmt, @bind) = $sql->select('table', $cols->[int(rand(20))], [map {_rand_where()} 1..int(rand(3)+1)]);
  10091         19908  
684 5000         5989110 $d->add($dbh->quote($stmt._random_str(5)));
685 5000         90354 my $dbh2 = DBI->connect( 'DBI:Mock:', '', '' );
686             }
687 1         555 return $d->hexdigest;
688             }
689              
690             sub bench_dct {
691 2     2 0 8 my $iter = shift;
692 2         32 my $d = Digest->new("MD5");
693 2         117 $d->add(bench_dct_sz(@$_)) foreach [$iter, 32], [$iter, 18], [$iter*8,8];
694              
695 2         56 return $d->hexdigest;
696             }
697              
698             sub bench_dct_sz {
699 6     6 0 41 my $iter = shift;
700 6         31 my $sz = shift;
701 6         76 my $d = Digest->new("MD5");
702 6         261 my @arrays;
703 6         181 push @arrays, [map { rand(256) } ( 1..$sz*$sz )] foreach 1..10;
  28240         39527  
704 6         40 foreach (1..$iter) {
705 500000         1008142 my $dct = dct2d($arrays[$iter % 10], $sz);
706 500000 100       16201531 $d->add($dct->[0]) if $_ % 10 == 1;
707             }
708 6         1041 return $d->hexdigest;
709             }
710              
711             sub bench_digest {
712 1     1 0 4 my $iter = shift;
713 1         6 my $str = _read_wiki_files();
714 1         16 my $d = Digest->new("MD5");
715 1         53 my $hex;
716 1         6 foreach (1..$iter) {
717 50         221 my $d2 = Digest->new("MD5");
718 50         428826 $d2->add($str);
719 50         473 $hex = $d2->hexdigest;
720 50         187 $d->add($hex);
721 50         233 $d2 = Digest->new("SHA-512");
722 50         1130910 $d2->add($str);
723 50         753 $hex = $d2->hexdigest;
724 50         288 $d->add($hex);
725 50         210 $d2 = Digest->new("SHA-1");
726 50         659103 $d2->add($str);
727 50         537 $hex = $d2->hexdigest;
728 50         450 $d->add($hex);
729             }
730 1         12 return $d->hexdigest;
731             }
732              
733             sub bench_encode {
734 1     1 0 4 my $iter = shift;
735 1         7 my $str = _read_wiki_files('utf8');
736 1         10 my $UTF8 = Encode::find_encoding('UTF-8');
737 1         67 my $UTF16 = Encode::find_encoding('UTF-16');
738 1         4028 our $cp1252 = Encode::find_encoding('cp-1252');
739 1         467 my $res = 'PASS';
740 1         5 my $unenc = 0;
741              
742 1         7 foreach (1..$iter) {
743 40         56786 my $bytes = encode_utf8($str);
744 40 50       16275 $res = 'Fail' unless length($bytes) > length($str);
745 40         52232 my $cp = decode_utf8($bytes);
746 40 100       178343 my $enc = rand(1) > 0.25 ? $UTF8 : $UTF16;
747 40         854638 $bytes = $enc->encode($cp);
748 40         521693 $cp = $enc->decode($bytes);
749 40 50       114927 $res = 'Fail' unless $cp eq $str;
750 40         935331 my $str2 = $cp1252->encode($cp);
751 40         1841733 $enc->encode($cp1252->decode($str2));
752 40         43836 $unenc = () = $str2 =~ /\?/g; # Non-encodable
753             }
754 1         16 return "$res $unenc";
755             }
756              
757             sub bench_imager {
758 2     2 0 18 my $iter = shift;
759 2         20 my $d = Digest->new("MD5");
760              
761 2         372 my $data;
762 2 100       264 open (my $fh, '<:raw', catfile($datadir,'M31.bmp')) or die $!;
763 1         10299 read($fh, $data, -s $fh);
764 1         33 close($fh);
765              
766 1         10 foreach (1..$iter) {
767 4 50       528761 my $img = Imager->new(data=>$data, type=>'bmp') or die Imager->errstr();
768 4         103982 my $thumb = $img->scale(scalefactor=>.3);
769 4         302209 my $newimg = $img->scale(scalefactor=>1.15);
770 4         696568 $newimg->filter(type=>'autolevels');
771 4         75479 $newimg->filter(type=>"gaussian", stddev=>0.5);
772 4         640102 $newimg->paste(left=>40,top=>20,img=>$thumb);
773 4         3005 $newimg->rubthrough(src=>$thumb,tx=>30, ty=>50);
774 4         2816 $newimg->compose(src => $thumb, tx => 30, ty => 20, combine => 'color');
775 4         36379 $newimg->flip(dir=>"h");
776 4         36258 $newimg->flip(dir=>"vh");
777 4         36288 $d->add(scalar(Image::PHash->new($newimg)->pHash));
778 4         154621 $newimg = $img->crop(left=>50, right=>100, top=>10, bottom=>100);
779 4         1050 $newimg = $img->crop(left=>50, top=>10, width=>50, height=>90);
780 4         697 $newimg = $img->copy();
781 4         27240 $newimg->filter(type=>"unsharpmask", stddev=>1, scale=>0.5);
782 4         754604 $newimg = $img->rotate(degrees=>20);
783 4         398874 $newimg->filter(type=>"contrast", intensity=>1.4);
784 4         133159 $newimg = $img->convert(matrix => [[0, 1, 0], [1, 0, 0], [0, 0, 1]]);
785 4         45952 $newimg = $img->convert(preset=>'grey');
786 4         44527 $d->add(scalar(Image::PHash->new($newimg)->pHash));
787 4         132407 $img->filter(type=>'mandelbrot');
788             }
789 1         176402 return $d->hexdigest;
790             }
791              
792             sub bench_json {
793 1     1 0 5 my $iter = shift;
794 1         6 my $res = 'PASS';
795 1         5 for (1..$iter) {
796 600         2061 my $len = int(rand(40)) + 1;
797 600         1629 my $obj = rand_hash($len);
798 600         23469 my $str = encode_json($obj);
799 600         1449 foreach (1..100) {
800 60000         2385851 $obj = decode_json($str);
801 60000         1973581 $str = encode_json($obj);
802             }
803 600         21667 my $obj2 = decode_json($str);
804 600 50       1677 $res = 'FAIL' unless compare_obj($obj, $obj2);
805             }
806 1         4 return $res;
807             }
808              
809             sub bench_jwt {
810 1     1 0 2 my $iter = shift;
811 1         9 my $d = Digest->new("MD5");
812 1         112 my $data = _random_str(5000);
813 1         11 my $rsa ='-----BEGIN PRIVATE KEY-----
814             MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqPfgaTEWEP3S9w0t
815             gsicURfo+nLW09/0KfOPinhYZ4ouzU+3xC4pSlEp8Ut9FgL0AgqNslNaK34Kq+NZ
816             jO9DAQIDAQABAkAgkuLEHLaqkWhLgNKagSajeobLS3rPT0Agm0f7k55FXVt743hw
817             Ngkp98bMNrzy9AQ1mJGbQZGrpr4c8ZAx3aRNAiEAoxK/MgGeeLui385KJ7ZOYktj
818             hLBNAB69fKwTZFsUNh0CIQEJQRpFCcydunv2bENcN/oBTRw39E8GNv2pIcNxZkcb
819             NQIgbYSzn3Py6AasNj6nEtCfB+i1p3F35TK/87DlPSrmAgkCIQDJLhFoj1gbwRbH
820             /bDRPrtlRUDDx44wHoEhSDRdy77eiQIgE6z/k6I+ChN1LLttwX0galITxmAYrOBh
821             BVl433tgTTQ=
822             -----END PRIVATE KEY-----';
823 1         4 my $key = '-----BEGIN PRIVATE KEY-----
824             MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYirTZSx+5O8Y6tlG
825             cka6W6btJiocdrdolfcukSoTEk+hRANCAAQkvPNu7Pa1GcsWU4v7ptNfqCJVq8Cx
826             zo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7fJDiFqz3
827             -----END PRIVATE KEY-----';
828 1         5 foreach (1..$iter) {
829 250         721 my $extra = _random_str(100);
830 250         978 my $data_in = $data.$extra;
831 250         844 my $token = encode_jwt(
832             payload => $data_in,
833             alg => 'ES256',
834             key => \$key,
835             );
836              
837 250         2181073 my $data_out = _decode_jwt2(token=>$token, key=>\$key);
838 250 50       1115 $d->add($token) if $data_in eq $data_out.$extra;
839              
840 250         889 $token = encode_jwt(
841             payload => $data_in,
842             alg => 'RS256',
843             key => \$rsa,
844             );
845              
846 250         211591 $data_out = _decode_jwt2(token=>$token, key=>\$rsa);
847 250 50       1336 $d->add($token) if $data_in eq $data_out.$extra;
848             }
849 1         31 return $d->hexdigest;
850             }
851              
852             sub bench_formattext {
853 1     1 0 5 my $iter = shift;
854 1         15 my $d = Digest->new("MD5");
855 1         76 my $file;
856 1         6 for (0..$iter-1) {
857 4         4430401 my $i = $_ % 2;
858 4         58 $file = catfile($datadir, "wiki$i.html");
859 4         59 my $tree = HTML::TreeBuilder->new->parse_file($file);
860 4         12109785 my $formatter = HTML::FormatText->new();
861 4         187 my $text = $formatter->format($tree);
862 4         8884045 $formatter = HTML::FormatText->new(leftmargin => 0, rightmargin => 30);
863 4         16347 $d->add(Encode::encode_utf8($formatter->format($tree)));
864             }
865 1         4965672 return $d->hexdigest;
866             }
867              
868             sub bench_matrixreal {
869 2     2 0 13 my $iter = shift;
870 2         46 my $d = Digest->new("MD5");
871 2         246 my $smatrix = Math::MatrixReal->new_random(13);
872 2         2485 my $matrix = Math::MatrixReal->new_random(20);
873 2         4765 my $bmatrix = Math::MatrixReal->new_random(72);
874              
875 2         49344 for (1..$iter) {
876 1300         16406 my $r = rand(10);
877 1300         9373 my $m1 = $r*$bmatrix;
878 1300         10110513 my $m2 = $bmatrix*$r;
879 1300         9790164 my $m3 = $bmatrix->multiply_scalar($bmatrix,$r);
880             # Should be zero
881 1300 100       8876181 $d->add($m1->element(1, 1) + $m2->element(1, 1) - 2 * $bmatrix->element(1, 1))
882             if $_ % 10 == 1;
883              
884 1300         15719 my $m = $matrix->det;
885 1300 100       16736480 $d->add($m) if $_ % 10 == 1;
886 1300         6611 $m =$matrix->decompose_LR->det_LR;
887 1300 100       16541820 $d->add($m) if $_ % 10 == 1;
888 1300         9929 $m1 = $matrix ** 2;
889 1300         17688720 $m2 = $matrix * $matrix;
890             #should be zero
891 1300 100       16961805 $d->add($m1->element(1, 1) - $m2->element(1, 1))
892             if $_ % 10 == 1;
893 1300         14560 $m1 = $smatrix->inverse();
894 1300         15568941 $m2 = $smatrix ** -1;
895 1300         15982546 $m3 = $smatrix->decompose_LR->invert_LR;
896 1300 100       15399409 $d->add($m1->element(1, 1), $m2->element(1, 1), $m3->element(1, 1))
897             if $_ % 10 == 1;
898             }
899              
900 2         360 return $d->hexdigest;
901             }
902              
903             sub bench_moose {
904 1     1 0 2 my $iter = shift;
905 1         3 my $cnt = 0;
906              
907 1         4 for (1..$iter) {
908 10000         4158705 my $p = rand(1000);
909 10000         29348 my $root = Benchmark::DKbench::MooseTree->new(node => 'root');
910 10000         11032447 $root->price($p);
911 10000         268103 $root->node;
912 10000         29989 $root->cost;
913 10000         35471 my $lchild = $root->left;
914 10000         277436 $lchild->node('child');
915 10000         267951 $lchild->price($p);
916 10000         262305 $lchild->tax;
917 10000         31008 my $child = $root->right;
918 10000         29150 $child->cost;
919 10000         33688 my $grandchild = $child->left;
920 10000         277958 $grandchild->node('grandchild');
921 10000         306414 $grandchild->has_parent;
922 10000         264996 $grandchild->parent;
923 10000         265593 $grandchild->price($p);
924 10000         28469 $grandchild->cost;
925 10000         32390 my $ggchild = $grandchild->right;
926 10000         30584 $ggchild->cost;
927 10000         43614 $cnt += 5;
928             }
929 1         428 return md5_hex("$cnt objects");
930             }
931              
932             sub bench_moose_prv {
933 2     2 0 7 my $iter = shift;
934 2         29 my $tdir = catfile($datadir, 't');
935 2         10 my $result;
936 2 100       13 if ($iter < 1) {
937 1         9 $tdir = catfile($tdir, 'recipes');
938 1         19199724 $result = `prove -rQ $tdir`;
939             } else {
940 1         73522895 $result = `prove -rQ $tdir` for (1..$iter);
941             }
942 2 50       167 if ($result =~ /Result: (\w*)/) {
943 2         97 return $1;
944             } else {
945 0         0 return '?';
946             }
947             }
948              
949             sub bench_primes_m {
950 1     1 0 17 my $iter = shift;
951 1         23 return bench_primes($iter, 7_500_000);
952             }
953              
954             sub bench_primes {
955 1     1 0 16 my $iter = shift;
956 1         20 my $max = shift;
957 1         23 my @primes;
958 1         29 @primes = _get_primes($max) for (1..$iter);
959 1         9312 return md5_hex(scalar(@primes)." primes up to $max");
960             }
961              
962             sub bench_regex_asc {
963 1     1 0 4 my $iter = shift;
964 1         15 return bench_regex_subst($iter, '');
965             }
966              
967             sub bench_regex_utf8 {
968 1     1 0 13 my $iter = shift;
969 1         13 return bench_regex_subst($iter, 'utf8');
970             }
971              
972             sub bench_regex_subst {
973 2     2 0 15 my $iter = shift;
974 2         23 my $enc = shift;
975 2         34 my $str = _read_wiki_files($enc);
976 2         22 my $match = bench_regex($str, $iter);
977 2         42 my $repl = bench_subst($str, $iter);
978 2         43 return md5_hex($match, $repl);
979             }
980              
981             sub bench_regex {
982 2     2 0 12 my $str = shift;
983 2         10 my $iter = shift;
984 2         7 my $count;
985 2         14 for (1..$iter) {
986 11         91 $count = 0;
987 11         1118350 $count += () = $str =~ /\b[A-Z][a-z]+/g;
988 11         1351781 $count += () = $str =~ /([\w\.+-]+)@[\w\.-]+\.([\w\.-]+)/g;
989 11         971892 $count += () = $str =~ m![\w]+://[^/\s?#]+[^\s?#]+(?:\?[^\s#]*)?(?:#[^\s]*)?!g;
990             }
991 2         31 return "$count Matched";
992             }
993              
994             sub bench_subst {
995 2     2 0 14 my $str = shift;
996 2         11 my $iter = shift;
997 2         15 my $count;
998 2         17 for (1..$iter) {
999 11         167 my $copy = $str;
1000 11         36 $count = 0;
1001 11         997583 while (my $s = $copy =~ s#<([^>]+)>([^>]*?)</\1>#$2#g) {
1002 44         3892791 $count += $s;
1003             }
1004 11         580580 $copy = substr($copy, int(rand(100))+1) for 1..10;
1005             }
1006 2         49 return "$count Replaced";
1007             }
1008              
1009             sub bench_textlevenshtein {
1010 1     1 0 14 my $iter = shift;
1011 1         46 my $d = Digest->new("MD5");
1012 1         137 my $data = _fuzzy_data();
1013 1         8 my $diff;
1014 1         6 foreach (1..$iter) {
1015 7         34 foreach my $sz (qw/10 100 1000 2500/) {
1016 28         108 my $n = scalar @{$data->{$sz}};
  28         172  
1017 28         149 my $i = int(rand($n));
1018             $diff = Text::Levenshtein::XS::distance(
1019             $data->{$sz}->[$i], $data->{$sz}->[$_]
1020 28         256 ) for 0..$n-1;
1021 28   100     1881763 $d->add($diff || -1);
1022 28 100       138 next if $sz > 1000;
1023             $diff = Text::Levenshtein::Damerau::XS::xs_edistance(
1024             $data->{$sz}->[$i], $data->{$sz}->[$_]
1025 21         179 ) for 0..$n-1;
1026 21         1675177 $d->add($diff);
1027             }
1028             }
1029 1         92 return $d->hexdigest;
1030             }
1031              
1032             sub bench_timepiece {
1033 1     1 0 20 my $iter = shift;
1034 1         27 my $t = Time::Piece::localtime(1692119499);
1035 1         873 my $d = Digest->new("MD5");
1036 1         59 my $day = 3600*24;
1037 1         32 local $ENV{TZ} = 'UTC';
1038              
1039 1         13 for (1..$iter) {
1040 75000         348203 $t += int(rand(1000)-500)*$day;
1041 75000 50       2459357 $t += 100000*$day if $t->year < 1970;
1042 75000         522500 my $str = $t->strftime("%w, %d %m %Y %H:%M:%S");
1043 75000         5910489 eval '$t = Time::Piece->strptime($str, "%w, %d %m %Y %H:%M:%S")';
1044 75000         1732448 my $jd = $t->julian_day;
1045 75000         6343128 $d->add($str,$jd);
1046             }
1047 1         25 return $d->hexdigest;
1048             }
1049              
1050             sub total_stats {
1051 2     2 0 25 my ($opt, $stats) = @_;
1052 2         27 my $benchmarks = benchmark_list();
1053 2 100       34 my $display = $opt->{time} ? 'times' : 'scores';
1054 2 100       26 my $title = $opt->{time} ? 'Time (sec)' : 'Score';
1055 2         23 print "Aggregates ($opt->{iter} iterations):\n".pad_to("Benchmark",24).pad_to("Avg $title").pad_to("Min $title").pad_to("Max $title");
1056 2 100       40 print pad_to("stdev %") if $opt->{stdev};
1057 2 100       32 print pad_to("Pass %") unless $opt->{time};
1058 2         33 print "\n";
1059 2         63 foreach my $bench (sort keys %$benchmarks) {
1060 42 100       155 next unless $stats->{$bench}->{$display};
1061 2         16 my $str = calc_stats($opt, $stats->{$bench}->{$display});
1062 2         20 print pad_to("$bench:",24).$str;
1063             print pad_to(
1064             sprintf("%d", 100 * ($opt->{iter}-($stats->{$bench}->{fail} || 0)) / $opt->{iter}))
1065 2 100 50     46 unless $opt->{time};
1066 2         30 print "\n";
1067             }
1068 2         22 my $str = calc_stats($opt, $stats->{total}->{$display});
1069 2         32 print pad_to("Overall Avg $title:", 24)."$str\n";
1070             }
1071              
1072             sub calc_stats {
1073 4     4 0 20 my $opt = shift;
1074 4         15 my $arr = shift;
1075 4         17 my $pad = shift;
1076 4         28 my ($min, $max, $avg) = min_max_avg($arr);
1077 4         17 my $str = join '', map {pad_to(sprintf($opt->{f}, $_), $pad)} ($avg,$min,$max);
  12         83  
1078 4 100 66     29 if ($opt->{stdev} && $avg) {
1079 2         19 my $stdev = avg_stdev($arr);
1080 2         14 $stdev *= 100/$avg;
1081 2         33 $str .= pad_to(sprintf("%0.2f%%", $stdev), $pad);
1082             }
1083 4         46 return $avg, $str;
1084             }
1085              
1086             sub min_max_avg {
1087 26     26 0 68 my $arr = shift;
1088 26 100       83 return (0, 0, 0) unless @$arr;
1089 25         219 return min(@$arr), max(@$arr), sum(@$arr)/scalar(@$arr);
1090             }
1091              
1092             sub avg_stdev {
1093 10     10 0 26 my $arr = shift;
1094 10 100       48 return (0, 0) unless @$arr;
1095 9         43 my $sum = sum(@$arr);
1096 9         35 my $avg = $sum/scalar(@$arr);
1097 9         24 my @sq;
1098 9         78 push @sq, ($avg - $_)**2 for (@$arr);
1099 9         33 my $dev = min_max_avg(\@sq);
1100 9         46 return $avg, sqrt($dev);
1101             }
1102              
1103             # $single = single tail of dist curve outlier, 1 for over (right), -1 for under (left)
1104             sub drop_outliers {
1105 7     7 0 25 my $arr = shift;
1106 7         21 my $single = shift;
1107 7         29 my ($avg, $stdev) = avg_stdev($arr);
1108 7         19 my @newarr;
1109 7         29 foreach (@$arr) {
1110 43 100       89 if ($single) {
1111 30 100       83 push @newarr, $_ unless $single*($_ - $avg) > 2*$stdev;
1112             } else {
1113 13 100       44 push @newarr, $_ unless abs($avg - $_) > 2*$stdev;
1114             }
1115             }
1116 7         48 return @newarr;
1117             }
1118              
1119             sub pad_to {
1120 118     118 0 346 my $str = shift;
1121 118   100     509 my $len = shift || 20;
1122 118         23163 return $str." "x($len-length($str));
1123             }
1124              
1125             sub _read_wiki_files {
1126 4   100 4   56 my $enc = shift || '';
1127 4         23 my $str = "";
1128 4         19 for (0..2) {
1129 12 50       1002 open my $fh, "<:$enc", catfile($datadir,"wiki$_.html") or die $!;
1130 12         66 $str .= do { local $/; <$fh> };
  12         72  
  12         34100  
1131             }
1132 4         30 return $str;
1133             }
1134              
1135             sub _random_str {
1136 5264   100 5264   18197 my $length = shift || 1;
1137 5264         8914 my $abc = shift;
1138 5264 100       14138 my ($base, $rng) = $abc ? (65, 26) : (32, 95);
1139 5264         9223 my $str = "";
1140 5264         46610 $str .= chr(int(rand($rng))+$base) for 1..$length;
1141 5264         37113 return $str;
1142             }
1143              
1144             sub _random_uchar {
1145 46303     46303   63384 my $chr = int(rand(800))+32;
1146 46303 100       74719 $chr += 128 if $chr > 127; # Skip Latin 1 supplement
1147 46303 100       69485 $chr += 288 if $chr > 591; # Skip pre-Greek blocks
1148 46303         119080 return chr($chr);
1149             }
1150              
1151             sub _fuzzy_data {
1152 1     1   8 my %data;
1153 100         241 push @{$data{10}}, join('', map {_random_uchar()} 1..(8+int(rand(5))))
  996         1535  
1154 1         13 for 0..99;
1155 1         13 push @{$data{100}}, $data{10}->[$_]x10 for 0..49;
  50         177  
1156 1         6 push @{$data{1000}}, _random_str(50,1)x20 for 0..7;
  8         35  
1157 1         6 push @{$data{2500}}, _random_str(50,1)x50 for 0..3;
  4         189  
1158 1         9 return \%data;
1159             }
1160              
1161             sub _rand_where {
1162 16477     16477   24911 my $p = rand();
1163 16477 100       36153 if ($p > 0.5) {
    100          
1164 8377         30562 return {foo => rand(10)};
1165             } elsif ($p > 0.2) {
1166 4922         28299 return {bar => {-in => [int($p*10)..int($p*20)]}};
1167             } else {
1168 3178 100       6608 my $op = $p > 0.1 ? '-and' : '-or';
1169 3178         6469 my @cond = map {_rand_where()} 1..int(rand(3)+1);
  6386         11255  
1170 3178         12547 return {$op => [@cond]};
1171             }
1172             }
1173              
1174             sub _db_data {
1175 1     1   6 my (@data, @cols);
1176 1         5 foreach (1..20) {
1177             my $d = {
1178             id => int(rand(10000000)),
1179             date => \"NOW()",
1180 20         54 map {"data".$_ => "foo bar" x int(rand(5)+1)} 1..int(rand(20)+1)
  202         705  
1181             };
1182 20         67 push @data, $d;
1183 20         158 push @cols, [sort keys %$d];
1184             }
1185 1         8 return \@data, \@cols;
1186             }
1187              
1188             sub compare_obj {
1189 28701     28701 0 41211 my ($obj1, $obj2) = @_;
1190 28701         37419 my $t1 = ref($obj1);
1191 28701         34395 my $t2 = ref($obj2);
1192 28701 100       45420 return 0 if $t1 ne $t2;
1193 28700 100       72327 return $obj1 eq $obj2 unless $t1;
1194 3496 100       7794 return $t1 eq 'ARRAY' ? compare_arr($obj1, $obj2) : compare_hash($obj1, $obj2);
1195             }
1196              
1197             sub compare_arr {
1198 1461     1461 0 2289 my ($arr1, $arr2) = @_;
1199 1461         2078 my $sz = scalar @$arr1;
1200 1461 100       2569 return 0 if $sz != scalar @$arr2;
1201 1460         2998 for (0..$sz-1) {
1202 8156 100       12684 return 0 unless compare_obj($arr1->[$_], $arr2->[$_]);
1203             }
1204 1459         3305 return 1;
1205             }
1206              
1207             sub compare_hash {
1208 2039     2039 0 3777 my ($h1, $h2) = @_;
1209 2039 100       5189 return 0 if scalar keys %$h1 != scalar keys %$h2;
1210 2038         7100 for (keys %$h1) {
1211 19944 100       32770 return 0 unless compare_obj($h1->{$_}, $h2->{$_});
1212             }
1213 2037         12626 return 1;
1214             }
1215              
1216             sub rand_arr {
1217 1468     1468 0 1966 my $sz = shift;
1218 1468         2020 my @arr;
1219 1468         2402 for (1..$sz) {
1220 8203         11862 my $len = int(rand(10)) + 1;
1221 8203 100       15239 my $item = rand(1) < 0.9 ? _random_uchar()x($len*5) : rand(1) < 0.5 ? rand_arr($len) : rand_hash($len);
    100          
1222 8203         14269 push @arr, $item;
1223             }
1224 1468         2339 return \@arr;
1225             }
1226              
1227             sub rand_hash {
1228 2044     2044 0 2965 my $sz = shift;
1229 2044         2740 my %hash;
1230 2044         3398 for (1..$sz) {
1231 20008         31160 my $len = int(rand(10)) + 1;
1232 20008 100       36975 my $item = rand(1) < 0.9 ? _random_uchar()x($len*5) : rand(1) < 0.5 ? rand_arr($len) : rand_hash($len);
    100          
1233 20008         32400 $hash{_random_uchar()x($len*4)} = $item;
1234             }
1235 2044         3881 return \%hash;
1236             }
1237              
1238             # modified from https://github.com/famzah/langs-performance/blob/master/primes.pl
1239             sub _get_primes {
1240 2     2   27 my $n = shift;
1241 2         19 my @s = ();
1242 2         36 for (my $i = 3; $i < $n + 1; $i += 2) {
1243 7499998         12822575 push(@s, $i);
1244             }
1245 2         30 my $mroot = $n**0.5;
1246 2         17 my $half = scalar @s;
1247 2         15 my $i = 0;
1248 2         6 my $m = 3;
1249 2         27 while ($m <= $mroot) {
1250 2736 100       5735 if ($s[$i]) {
1251 796         2587 for (my $j = int(($m * $m - 3) / 2); $j < $half; $j += $m) {
1252 13244400         24848962 $s[$j] = 0;
1253             }
1254             }
1255 2736         3701 $i++;
1256 2736         5679 $m = 2 * $i + 3;
1257             }
1258              
1259 2         655628 return 2, grep($_, @s);
1260             }
1261              
1262             # Fix for Crypt::JWT that was submitted as a patch. Will remove if it is merged.
1263             sub _decode_jwt2 {
1264 501     501   1915 my %args = @_;
1265 501         934 my ($header, $payload);
1266              
1267 501 100       9726 if ($args{token} =~
1268             /^([a-zA-Z0-9_-]+)=*\.([a-zA-Z0-9_-]*)=*\.([a-zA-Z0-9_-]*)=*(?:\.([a-zA-Z0-9_-]+)=*\.([a-zA-Z0-9_-]+)=*)?$/
1269             ) {
1270 500 50       2191 if (length($5)) {
1271             # JWE token (5 segments)
1272 0         0 ($header, $payload) =
1273             Crypt::JWT::_decode_jwe($1, $2, $3, $4, $5, undef, {}, {},
1274             %args);
1275             } else {
1276             # JWS token (3 segments)
1277 500         2463 ($header, $payload) =
1278             Crypt::JWT::_decode_jws($1, $2, $3, {}, %args);
1279             }
1280             }
1281 501 100       2050748 return ($header, $payload) if $args{decode_header};
1282 500         1804 return $payload;
1283             }
1284              
1285             sub _get_time {
1286 45 50   45   509 return $mono_clock ? Time::HiRes::clock_gettime(CLOCK_MONOTONIC) : Time::HiRes::time();
1287             }
1288              
1289             # Helper package for Moose benchmark
1290              
1291             {
1292             package Benchmark::DKbench::MooseTree;
1293              
1294 3     3   2030 use Moose;
  3         1335921  
  3         21  
1295              
1296             has 'price' => (is => 'rw', default => 10);
1297             has 'tax' => (is => 'rw', lazy_build => 1);
1298             has 'node' => (is => 'rw', isa => 'Any');
1299             has 'parent' => (
1300             is => 'rw',
1301             isa => 'Benchmark::DKbench::MooseTree',
1302             predicate => 'has_parent',
1303             weak_ref => 1,
1304             );
1305             has 'left' => (
1306             is => 'rw',
1307             isa => 'Benchmark::DKbench::MooseTree',
1308             predicate => 'has_left',
1309             lazy => 1,
1310             builder => '_build_child_tree',
1311             );
1312             has 'right' => (
1313             is => 'rw',
1314             isa => 'Benchmark::DKbench::MooseTree',
1315             predicate => 'has_right',
1316             lazy => 1,
1317             builder => '_build_child_tree',
1318             );
1319             before 'right', 'left' => sub {
1320             my ($self, $tree) = @_;
1321             $tree->parent($self) if defined $tree;
1322             };
1323              
1324             sub _build_tax {
1325 50000     50000   80542 my $self = shift;
1326 50000         1295193 $self->price * 0.2;
1327             }
1328              
1329             sub _build_child_tree {
1330 40000     40000   67233 my $self = shift;
1331 40000         122664 return Benchmark::DKbench::MooseTree->new( parent => $self );
1332             }
1333              
1334             sub cost {
1335 40000     40000 0 74290 my $self = shift;
1336 40000         1058856 $self->price + $self->tax;
1337             }
1338             }
1339              
1340             1;