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