• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env perl
2# * © 2016 and later: Unicode, Inc. and others.
3# * License & terms of use: http://www.unicode.org/copyright.html
4# *******************************************************************************
5# * Copyright (C) 2002-2007 International Business Machines Corporation and     *
6# * others. All Rights Reserved.                                                *
7# *******************************************************************************
8
9use strict;
10
11# Assume we are running within the icu4j root directory
12use lib 'src/com/ibm/icu/dev/test/perf';
13use Dataset;
14
15#---------------------------------------------------------------------
16# Test class
17my $TESTCLASS = 'com.ibm.icu.dev.test.perf.DateFormatPerformanceTest';
18
19my $OS=$^O;
20my $CLASSPATH;
21if ($^O eq "MSWin32") {
22	$CLASSPATH = './target/*;./target/dependency/*';
23} else {
24	$CLASSPATH = './target/*:./target/dependency/*';
25}
26
27# Methods to be tested.  Each pair represents a test method and
28# a baseline method which is used for comparison.
29my @METHODS  = (
30                 ['TestJDKConstruction',     'TestICUConstruction'],
31                 ['TestJDKParse',            'TestICUParse'],
32                 ['TestJDKFormat',           'TestICUFormat']
33               );
34# Patterns which define the set of characters used for testing.
35my @OPTIONS = (
36#                 locale    pattern              date string
37                [ "en_US",  "dddd MMM yyyy",     "15 Jan 2007"],
38                [ "sw_KE",  "dddd MMM yyyy",     "15 Jan 2007"],
39                [ "en_US",  "HH:mm",             "13:13"],
40                [ "en_US",  "HH:mm zzzz",        "13:13 Pacific Standard Time"],
41                [ "en_US",  "HH:mm z",           "13:13 PST"],
42                [ "en_US",  "HH:mm Z",           "13:13 -0800"],
43              );
44
45my $THREADS;        # number of threads (input from command-line args)
46my $CALIBRATE = 2;  # duration in seconds for initial calibration
47my $DURATION  = 10; # duration in seconds for each pass
48my $NUMPASSES = 4;  # number of passes.  If > 1 then the first pass
49                    # is discarded as a JIT warm-up pass.
50
51my $TABLEATTR = 'BORDER="1" CELLPADDING="4" CELLSPACING="0"';
52
53my $PLUS_MINUS = "±";
54
55if ($NUMPASSES < 3) {
56    die "Need at least 3 passes.  One is discarded (JIT warmup) and need two to have 1 degree of freedom (t distribution).";
57}
58
59my $OUT; # see out()
60
61# run all tests with the specified number of threads from command-line input
62# (if there is no arguments, use $THREADS = 1)
63foreach my $arg ($#ARGV >= 0 ? @ARGV : "1") {
64  $THREADS = $arg;
65  main();
66}
67
68
69#---------------------------------------------------------------------
70# ...
71sub main {
72    my $date = localtime;
73    my $threads = ($THREADS > 1) ? "($THREADS threads)" : "";
74    my $title = "ICU4J Performance Test $threads $date";
75
76    my $html = $date;
77    $html =~ s/://g; # ':' illegal
78    $html =~ s/\s*\d+$//; # delete year
79    $html =~ s/^\w+\s*//; # delete dow
80    $html = "perf $html.html";
81
82    open(HTML,">$html") or die "Can't write to $html: $!";
83
84    print HTML <<EOF;
85<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
86   "http://www.w3.org/TR/html4/strict.dtd">
87<HTML>
88   <HEAD>
89      <TITLE>$title</TITLE>
90   </HEAD>
91   <BODY>
92EOF
93    print HTML "<H1>$title</H1>\n";
94
95    print HTML "<H2>$TESTCLASS</H2>\n";
96
97    my $raw = "";
98
99    for my $methodPair (@METHODS) {
100
101        my $testMethod = $methodPair->[0];
102        my $baselineMethod = $methodPair->[1];
103
104        print HTML "<P><TABLE $TABLEATTR><TR><TD>\n";
105        print HTML "<P><B>$testMethod vs. $baselineMethod</B></P>\n";
106
107        print HTML "<P><TABLE $TABLEATTR BGCOLOR=\"#CCFFFF\">\n";
108        print HTML "<TR><TD>Options</TD><TD>$testMethod</TD>";
109        print HTML "<TD>$baselineMethod</TD><TD>Ratio</TD></TR>\n";
110
111        $OUT = '';
112
113        for my $pat (@OPTIONS) {
114            print HTML "<TR><TD>@$pat[0], \"@$pat[1]\", \"@$pat[2]\"</TD>\n";
115
116            out("<P><TABLE $TABLEATTR WIDTH=\"100%\">");
117
118            # measure the test method
119            out("<TR><TD>");
120            print "\n$testMethod [@$pat]\n";
121            my $t = measure2($testMethod, $pat, -$DURATION);
122            out("</TD></TR>");
123            print HTML "<TD>", formatSeconds(4, $t->getMean(), $t->getError);
124            print HTML "/event</TD>\n";
125
126            # measure baseline method
127            out("<TR><TD>");
128            print "\n$baselineMethod [@$pat]\n";
129            my $b = measure2($baselineMethod, $pat, -$DURATION);
130            out("</TD></TR>");
131            print HTML "<TD>", formatSeconds(4, $b->getMean(), $t->getError);
132            print HTML "/event</TD>\n";
133
134            out("</TABLE></P>");
135
136            # output ratio
137            my $r = $t->divide($b);
138            my $mean = $r->getMean() - 1;
139            my $color = $mean < 0 ? "RED" : "BLACK";
140            print HTML "<TD><B><FONT COLOR=\"$color\">", formatPercent(3, $mean, $r->getError);
141            print HTML "</FONT></B></TD></TR>\n";
142        }
143
144        print HTML "</TABLE></P>\n";
145
146        print HTML "<P>Raw data:</P>\n";
147        print HTML $OUT;
148        print HTML "</TABLE></P>\n";
149    }
150
151    print HTML <<EOF;
152   </BODY>
153</HTML>
154EOF
155    close(HTML) or die "Can't close $html: $!";
156}
157
158#---------------------------------------------------------------------
159# Append text to the global variable $OUT
160sub out {
161    $OUT .= join('', @_);
162}
163
164#---------------------------------------------------------------------
165# Append text to the global variable $OUT
166sub outln {
167    $OUT .= join('', @_) . "\n";
168}
169
170#---------------------------------------------------------------------
171# Measure a given test method with a give test pattern using the
172# global run parameters.
173#
174# @param the method to run
175# @param the pattern defining characters to test
176# @param if >0 then the number of iterations per pass.  If <0 then
177#        (negative of) the number of seconds per pass.
178#
179# @return a Dataset object, scaled by iterations per pass and
180#         events per iteration, to give time per event
181#
182sub measure2 {
183    my @data = measure1(@_);
184    my $iterPerPass = shift(@data);
185    my $eventPerIter = shift(@data);
186
187    shift(@data) if (@data > 1); # discard first run
188
189    my $ds = Dataset->new(@data);
190    $ds->setScale(1.0e-3 / ($iterPerPass * $eventPerIter));
191    $ds;
192}
193
194#---------------------------------------------------------------------
195# Measure a given test method with a give test pattern using the
196# global run parameters.
197#
198# @param the method to run
199# @param the pattern defining characters to test
200# @param if >0 then the number of iterations per pass.  If <0 then
201#        (negative of) the number of seconds per pass.
202#
203# @return array of:
204#         [0] iterations per pass
205#         [1] events per iteration
206#         [2..] ms reported for each pass, in order
207#
208sub measure1 {
209    my $method = shift;
210    my $pat = shift;
211    my $iterCount = shift; # actually might be -seconds/pass
212
213    out("<P>Measuring $method for input file @$pat[0] for encoding @$pat[2] , ");
214    if ($iterCount > 0) {
215        out("$iterCount iterations/pass, $NUMPASSES passes</P>\n");
216    } else {
217        out(-$iterCount, " seconds/pass, $NUMPASSES passes</P>\n");
218    }
219
220    # is $iterCount actually -seconds/pass?
221    if ($iterCount < 0) {
222
223        # calibrate: estimate ms/iteration
224        print "Calibrating...";
225        my @t = callJava($method, $pat, -$CALIBRATE, 1);
226        print "done.\n";
227
228        my @data = split(/\s+/, $t[0]->[2]);
229        $data[0] *= 1.0e+3;
230
231        my $timePerIter = 1.0e-3 * $data[0] / $data[1];
232
233        # determine iterations/pass
234        $iterCount = int(-$iterCount / $timePerIter + 0.5);
235
236        out("<P>Calibration pass ($CALIBRATE sec): ");
237        out("$data[0] ms, ");
238        out("$data[1] iterations = ");
239        out(formatSeconds(4, $timePerIter), "/iteration<BR>\n");
240    }
241
242    # run passes
243    print "Measuring $iterCount iterations x $NUMPASSES passes...";
244    my @t = callJava($method, $pat, $iterCount, $NUMPASSES);
245    print "done.\n";
246    my @ms = ();
247    my @b; # scratch
248    for my $a (@t) {
249        # $a->[0]: method name, corresponds to $method
250        # $a->[1]: 'begin' data, == $iterCount
251        # $a->[2]: 'end' data, of the form <ms> <loops> <eventsPerIter>
252        # $a->[3...]: gc messages from JVM during pass
253        @b = split(/\s+/, $a->[2]);
254        push(@ms, $b[0] * 1.0e+3);
255    }
256    my $eventsPerIter = $b[2];
257
258    out("Iterations per pass: $iterCount<BR>\n");
259    out("Events per iteration: $eventsPerIter<BR>\n");
260
261    my @ms_str = @ms;
262    $ms_str[0] .= " (discarded)" if (@ms_str > 1);
263    out("Raw times (ms/pass): ", join(", ", @ms_str), "<BR>\n");
264
265    ($iterCount, $eventsPerIter, @ms);
266}
267
268#---------------------------------------------------------------------
269# Invoke java to run $TESTCLASS, passing it the given parameters.
270#
271# @param the method to run
272# @param the number of iterations, or if negative, the duration
273#        in seconds.  If more than on pass is desired, pass in
274#        a string, e.g., "100 100 100".
275# @param the pattern defining characters to test
276#
277# @return an array of results.  Each result is an array REF
278#         describing one pass.  The array REF contains:
279#         ->[0]: The method name as reported
280#         ->[1]: The params on the '= <meth> begin ...' line
281#         ->[2]: The params on the '= <meth> end ...' line
282#         ->[3..]: GC messages from the JVM, if any
283#
284sub callJava {
285    my $method = shift;
286    my $pat = shift;
287    my $n = shift;
288    my $passes = shift;
289
290    my $n = ($n < 0) ? "-t ".(-$n) : "-i ".$n;
291
292    my $cmd = "java -classpath $CLASSPATH $TESTCLASS $method $n -p $passes -L @$pat[0] \"@$pat[1]\" \"@$pat[2]\" -r $THREADS";
293    print "[$cmd]\n"; # for debugging
294    open(PIPE, "$cmd|") or die "Can't run \"$cmd\"";
295    my @out;
296    while (<PIPE>) {
297        push(@out, $_);
298    }
299    close(PIPE) or die "Java failed: \"$cmd\"";
300
301    @out = grep(!/^\#/, @out);  # filter out comments
302
303    #print "[", join("\n", @out), "]\n";
304
305    my @results;
306    my $method = '';
307    my $data = [];
308    foreach (@out) {
309        next unless (/\S/);
310
311        if (/^=\s*(\w+)\s*(\w+)\s*(.*)/) {
312            my ($m, $state, $d) = ($1, $2, $3);
313            #print "$_ => [[$m $state $data]]\n";
314            if ($state eq 'begin') {
315                die "$method was begun but not finished" if ($method);
316                $method = $m;
317                push(@$data, $d);
318                push(@$data, ''); # placeholder for end data
319            } elsif ($state eq 'end') {
320                if ($m ne $method) {
321                    die "$method end does not match: $_";
322                }
323                $data->[1] = $d; # insert end data at [1]
324                #print "#$method:", join(";",@$data), "\n";
325                unshift(@$data, $method); # add method to start
326
327                push(@results, $data);
328                $method = '';
329                $data = [];
330            } else {
331                die "Can't parse: $_";
332            }
333        }
334
335        elsif (/^\[/) {
336            if ($method) {
337                push(@$data, $_);
338            } else {
339                # ignore extraneous GC notices
340            }
341        }
342
343        else {
344            die "Can't parse: $_";
345        }
346    }
347
348    die "$method was begun but not finished" if ($method);
349
350    @results;
351}
352
353#|#---------------------------------------------------------------------
354#|# Format a confidence interval, as given by a Dataset.  Output is as
355#|# as follows:
356#|#   241.23 - 241.98 => 241.5 +/- 0.3
357#|#   241.2 - 243.8 => 242 +/- 1
358#|#   211.0 - 241.0 => 226 +/- 15 or? 230 +/- 20
359#|#   220.3 - 234.3 => 227 +/- 7
360#|#   220.3 - 300.3 => 260 +/- 40
361#|#   220.3 - 1000 => 610 +/- 390 or? 600 +/- 400
362#|#   0.022 - 0.024 => 0.023 +/- 0.001
363#|#   0.022 - 0.032 => 0.027 +/- 0.005
364#|#   0.022 - 1.000 => 0.5 +/- 0.5
365#|# In other words, take one significant digit of the error value and
366#|# display the mean to the same precision.
367#|sub formatDataset {
368#|    my $ds = shift;
369#|    my $lower = $ds->getMean() - $ds->getError();
370#|    my $upper = $ds->getMean() + $ds->getError();
371#|    my $scale = 0;
372#|    # Find how many initial digits are the same
373#|    while ($lower < 1 ||
374#|           int($lower) == int($upper)) {
375#|        $lower *= 10;
376#|        $upper *= 10;
377#|        $scale++;
378#|    }
379#|    while ($lower >= 10 &&
380#|           int($lower) == int($upper)) {
381#|        $lower /= 10;
382#|        $upper /= 10;
383#|        $scale--;
384#|    }
385#|}
386
387#---------------------------------------------------------------------
388# Format a number, optionally with a +/- delta, to n significant
389# digits.
390#
391# @param significant digit, a value >= 1
392# @param multiplier
393# @param time in seconds to be formatted
394# @optional delta in seconds
395#
396# @return string of the form "23" or "23 +/- 10".
397#
398sub formatNumber {
399    my $sigdig = shift;
400    my $mult = shift;
401    my $a = shift;
402    my $delta = shift; # may be undef
403
404    my $result = formatSigDig($sigdig, $a*$mult);
405    if (defined($delta)) {
406        my $d = formatSigDig($sigdig, $delta*$mult);
407        # restrict PRECISION of delta to that of main number
408        if ($result =~ /\.(\d+)/) {
409            # TODO make this work for values with all significant
410            # digits to the left of the decimal, e.g., 1234000.
411
412            # TODO the other thing wrong with this is that it
413            # isn't rounding the $delta properly.  Have to put
414            # this logic into formatSigDig().
415            my $x = length($1);
416            $d =~ s/\.(\d{$x})\d+/.$1/;
417        }
418        $result .= " $PLUS_MINUS " . $d;
419    }
420    $result;
421}
422
423#---------------------------------------------------------------------
424# Format a time, optionally with a +/- delta, to n significant
425# digits.
426#
427# @param significant digit, a value >= 1
428# @param time in seconds to be formatted
429# @optional delta in seconds
430#
431# @return string of the form "23 ms" or "23 +/- 10 ms".
432#
433sub formatSeconds {
434    my $sigdig = shift;
435    my $a = shift;
436    my $delta = shift; # may be undef
437
438    my @MULT = (1   , 1e3,  1e6,  1e9);
439    my @SUFF = ('s' , 'ms', 'us', 'ns');
440
441    # Determine our scale
442    my $i = 0;
443    ++$i while ($a*$MULT[$i] < 1 && $i < @MULT);
444
445    formatNumber($sigdig, $MULT[$i], $a, $delta) . ' ' . $SUFF[$i];
446}
447
448#---------------------------------------------------------------------
449# Format a percentage, optionally with a +/- delta, to n significant
450# digits.
451#
452# @param significant digit, a value >= 1
453# @param value to be formatted, as a fraction, e.g. 0.5 for 50%
454# @optional delta, as a fraction
455#
456# @return string of the form "23 %" or "23 +/- 10 %".
457#
458sub formatPercent {
459    my $sigdig = shift;
460    my $a = shift;
461    my $delta = shift; # may be undef
462
463    formatNumber($sigdig, 100, $a, $delta) . ' %';
464}
465
466#---------------------------------------------------------------------
467# Format a number to n significant digits without using exponential
468# notation.
469#
470# @param significant digit, a value >= 1
471# @param number to be formatted
472#
473# @return string of the form "1234" "12.34" or "0.001234".  If
474#         number was negative, prefixed by '-'.
475#
476sub formatSigDig {
477    my $n = shift() - 1;
478    my $a = shift;
479
480    local $_ = sprintf("%.${n}e", $a);
481    my $sign = (s/^-//) ? '-' : '';
482
483    my $a_e;
484    my $result;
485    if (/^(\d)\.(\d+)e([-+]\d+)$/) {
486        my ($d, $dn, $e) = ($1, $2, $3);
487        $a_e = $e;
488        $d .= $dn;
489        $e++;
490        $d .= '0' while ($e > length($d));
491        while ($e < 1) {
492            $e++;
493            $d = '0' . $d;
494        }
495        if ($e == length($d)) {
496            $result = $sign . $d;
497        } else {
498            $result = $sign . substr($d, 0, $e) . '.' . substr($d, $e);
499        }
500    } else {
501        die "Can't parse $_";
502    }
503    $result;
504}
505
506#eof
507