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