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