| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of BenchmarkTests; | |
| 6 | |
| 7 /** | |
| 8 * The results of a single block of tests (count times run, overall time). | |
| 9 */ | |
| 10 class BlockSample { | |
| 11 BlockSample(this.count, this.durationNanos); | |
| 12 int count; | |
| 13 int durationNanos; | |
| 14 | |
| 15 static int _totalCount(List<BlockSample> samples) => | |
| 16 _sum(samples, int (BlockSample s) => s.count); | |
| 17 | |
| 18 static int _totalTime(List<BlockSample> samples) => | |
| 19 _sum(samples, int (BlockSample s) => s.durationNanos); | |
| 20 | |
| 21 static BlockSample _select(List<BlockSample> samples, | |
| 22 BlockSample selector(BlockSample a, BlockSample b)) { | |
| 23 BlockSample r = null; | |
| 24 for (BlockSample s in samples) { | |
| 25 r = (r == null) ? s : selector(r, s); | |
| 26 } | |
| 27 return r; | |
| 28 } | |
| 29 | |
| 30 static int _sum(List<BlockSample> samples, int extract(BlockSample s)) { | |
| 31 int total = 0; | |
| 32 for (BlockSample s in samples) { | |
| 33 total += extract(s); | |
| 34 } | |
| 35 return total; | |
| 36 } | |
| 37 } | |
| 38 | |
| 39 /** | |
| 40 * Uses sample data to build a performance model for a test. Construct | |
| 41 * the model from a set of sample results, and it generates a simple | |
| 42 * predivtive model for execution of future requests. It uses | |
| 43 * a simple least-squares linear solution to build the model. | |
| 44 */ | |
| 45 class PerformanceModel { | |
| 46 PerformanceModel.calculate(List<BlockSample> source) { | |
| 47 if (0 == source.length) { | |
| 48 throw "Missing data exception"; | |
| 49 } else if (1 == source.length) { | |
| 50 overheadNanos = 0; | |
| 51 perRequestNanos = source[0].durationNanos / source[0].count; | |
| 52 } else { | |
| 53 double n = source.length.toDouble(); | |
| 54 double sumY = BlockSample._totalTime(source).toDouble(); | |
| 55 double sumXSquared = BlockSample._sum(source, | |
| 56 int _(BlockSample s) => s.count * s.count).toDouble(); | |
| 57 double sumX = BlockSample._totalCount(source).toDouble(); | |
| 58 double sumXY = BlockSample._sum(source, | |
| 59 int _(BlockSample s) => s.durationNanos * s.count).toDouble(); | |
| 60 | |
| 61 overheadNanos = | |
| 62 ((((sumY * sumXSquared) - (sumX * sumXY)) / | |
| 63 ((n * sumXSquared) - (sumX * sumX))) / source.length).toInt(); | |
| 64 | |
| 65 perRequestNanos = | |
| 66 (((n * sumXY) - (sumX * sumY)) / | |
| 67 ((n * sumXSquared) - (sumX * sumX))).toInt(); | |
| 68 } | |
| 69 } | |
| 70 | |
| 71 bool isValid() => overheadNanos >= 0 && perRequestNanos >= 0; | |
| 72 | |
| 73 int overheadNanos; | |
| 74 int perRequestNanos; | |
| 75 int repsFor(int targetDurationNanos, [int blocksize = -1]) { | |
| 76 if (blocksize <= 0) { | |
| 77 return ((targetDurationNanos - overheadNanos) / perRequestNanos).toInt(); | |
| 78 } else { | |
| 79 int blockTime = overheadNanos + (blocksize * perRequestNanos); | |
| 80 int fullBlocks = targetDurationNanos ~/ blockTime; | |
| 81 int extraReps = | |
| 82 ((targetDurationNanos - (fullBlocks * blockTime)) - overheadNanos) | |
| 83 ~/ perRequestNanos; | |
| 84 return ((fullBlocks * blocksize) + extraReps).toInt(); | |
| 85 } | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 /** | |
| 90 * Report overall test performance | |
| 91 */ | |
| 92 class TestReport { | |
| 93 TestReport(this.id, this.desc, this.warmup, this.results) { | |
| 94 spaceChar = " ".codeUnits[0]; | |
| 95 } | |
| 96 | |
| 97 int spaceChar; | |
| 98 | |
| 99 int resultsCount() => BlockSample._totalCount(results); | |
| 100 | |
| 101 int resultsNanos() => BlockSample._totalTime(results); | |
| 102 | |
| 103 int resultsBestNanos() { | |
| 104 BlockSample best = bestBlock(results); | |
| 105 return best.durationNanos ~/ best.count; | |
| 106 } | |
| 107 | |
| 108 int resultsMeanNanos() => | |
| 109 BlockSample._totalTime(results) ~/ BlockSample._totalCount(results); | |
| 110 | |
| 111 int resultsWorstNanos() { | |
| 112 BlockSample worst = worstBlock(results); | |
| 113 return worst.durationNanos / worst.count; | |
| 114 } | |
| 115 | |
| 116 int warmupBestNanos() { | |
| 117 BlockSample best = bestBlock(warmup); | |
| 118 return best.durationNanos / best.count; | |
| 119 } | |
| 120 | |
| 121 int warmupMeanNanos() => _totalTime(warmup) / _totalCount(warmup); | |
| 122 | |
| 123 int warmupWorstNanos() { | |
| 124 BlockSample worst = worstBlock(warmup); | |
| 125 return worst.durationNanos / worst.count; | |
| 126 } | |
| 127 | |
| 128 BlockSample bestBlock(List<BlockSample> samples) { | |
| 129 return BlockSample._select(samples, | |
| 130 BlockSample selector(BlockSample a, BlockSample b) { | |
| 131 return a.durationNanos <= b.durationNanos ? a : b; | |
| 132 }); | |
| 133 } | |
| 134 | |
| 135 BlockSample worstBlock(List<BlockSample> samples) { | |
| 136 return BlockSample._select(samples, | |
| 137 BlockSample selector(BlockSample a, BlockSample b) { | |
| 138 return a.durationNanos >= b.durationNanos ? a : b; | |
| 139 }); | |
| 140 } | |
| 141 | |
| 142 void printReport() { | |
| 143 String text = _leftAlign("${id}", 30); | |
| 144 String totalCount = _rightAlign(resultsCount().toString(), 10); | |
| 145 String totalDurationMs = | |
| 146 _rightAlign(_stringifyDoubleAsInt(resultsNanos() / 1E6), 6); | |
| 147 String meanDuration = | |
| 148 _rightAlign(_stringifyDoubleAsInt(resultsMeanNanos().toDouble()), 8); | |
| 149 | |
| 150 print("${text} total time:${totalDurationMs} ms" + | |
| 151 " iterations:${totalCount} mean:${meanDuration} ns"); | |
| 152 } | |
| 153 | |
| 154 void printReportWithThroughput(int sizeBytes) { | |
| 155 String text = _leftAlign("${id}", 30); | |
| 156 String totalCount = _rightAlign(resultsCount().toString(), 10); | |
| 157 String totalDurationMs = | |
| 158 _rightAlign(_stringifyDoubleAsInt(resultsNanos() / 1E6), 6); | |
| 159 String meanDuration = | |
| 160 _rightAlign(_stringifyDoubleAsInt(resultsMeanNanos()), 8); | |
| 161 | |
| 162 int totalBytes = sizeBytes * resultsCount(); | |
| 163 String mbPerSec = (((1E9 * sizeBytes * resultsCount()) / | |
| 164 (1024 * 1024 * resultsNanos()))).toString(); | |
| 165 print("${text} total time:${totalDurationMs} ms" + | |
| 166 " iterations:${totalCount}" + | |
| 167 " mean:${meanDuration} ns; ${mbPerSec} MB/sec"); | |
| 168 } | |
| 169 | |
| 170 String _leftAlign(String s, int width) { | |
| 171 List<int> outCodes = new List<int>.filled(width, spaceChar); | |
| 172 outCodes.setRange(0, Math.min(width, s.length), s.codeUnits); | |
| 173 return new String.fromCharCodes(outCodes); | |
| 174 } | |
| 175 | |
| 176 String _rightAlign(String s, int width) { | |
| 177 List<int> outCodes = new List<int>.filled(width, spaceChar); | |
| 178 int fromIndex = Math.max(0, width - s.length); | |
| 179 int length = Math.min(width, s.length); | |
| 180 outCodes.setRange(fromIndex, fromIndex + length, s.codeUnits); | |
| 181 return new String.fromCharCodes(outCodes); | |
| 182 } | |
| 183 | |
| 184 static String _stringifyDoubleAsInt(double val) { | |
| 185 if (val.isInfinite || val.isNaN) { | |
| 186 return "NaN"; | |
| 187 } else { | |
| 188 return val.toInt().toString(); | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 String id; | |
| 193 String desc; | |
| 194 List<BlockSample> warmup; | |
| 195 List<BlockSample> results; | |
| 196 } | |
| 197 | |
| 198 class Runner { | |
| 199 static List<String> arguments; // Set by main. | |
| 200 | |
| 201 static bool runTest(String testId) { | |
| 202 return arguments.length == 0 || | |
| 203 arguments.any((String id) => id == testId); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 /** | |
| 208 * Run traditional blocking-style tests. Tests may be run a specified number | |
| 209 * of times, or they can be run based on performance to estimate a particular | |
| 210 * duration. | |
| 211 */ | |
| 212 class BenchmarkRunner extends Runner { | |
| 213 static void runCount(String id, String desc, CountTestConfig config, | |
| 214 Function test) { | |
| 215 if (runTest(id)) { | |
| 216 List<BlockSample> warmupSamples = _runTests(test, config._warmup, 1); | |
| 217 List<BlockSample> resultSamples = _runTests(test, config._reps, 1); | |
| 218 config.reportHandler( | |
| 219 new TestReport(id, desc, warmupSamples, resultSamples)); | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 static void runTimed(String id, String desc, TimedTestConfig config, | |
| 224 Function test) { | |
| 225 if (runTest(id)) { | |
| 226 List<BlockSample> warmupSamples = _runTests(test, config._warmup, 1); | |
| 227 PerformanceModel model = _calibrate(config._minSampleTimeMs, 16, test); | |
| 228 int reps = model.repsFor(1E6 * config._targetTimeMs, config._blocksize); | |
| 229 int blocksize = config._blocksize < 0 ? reps : config._blocksize; | |
| 230 List<BlockSample> resultSamples = _runTests(test, reps, blocksize); | |
| 231 config.reportHandler( | |
| 232 new TestReport(id, desc, warmupSamples, resultSamples)); | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 static PerformanceModel _calibrate(int minSampleTimeMs, int maxAttempts, | |
| 237 Function test) { | |
| 238 PerformanceModel model; | |
| 239 int i = 0; | |
| 240 do { | |
| 241 model = _buildPerformanceModel(minSampleTimeMs, test); | |
| 242 i++; | |
| 243 } while (i < maxAttempts && !model.isValid()); | |
| 244 return model; | |
| 245 } | |
| 246 | |
| 247 static PerformanceModel _buildPerformanceModel( | |
| 248 int minSampleTimeMs, Function test) { | |
| 249 int iterations = 1; | |
| 250 List<BlockSample> calibrationResults = []; | |
| 251 BlockSample calibration = _execBlock(test, iterations); | |
| 252 calibrationResults.add(calibration); | |
| 253 while (calibration.durationNanos < (1E6 * minSampleTimeMs)) { | |
| 254 iterations *= 2; | |
| 255 calibration = _execBlock(test, iterations); | |
| 256 calibrationResults.add(calibration); | |
| 257 } | |
| 258 return new PerformanceModel.calculate(calibrationResults); | |
| 259 } | |
| 260 | |
| 261 static List<BlockSample> _runTests(Function test, int count, int blocksize) { | |
| 262 List<BlockSample> samples = []; | |
| 263 for (int rem = count; rem > 0; rem -= blocksize) { | |
| 264 BlockSample bs = _execBlock(test, Math.min(blocksize, rem)); | |
| 265 samples.add(bs); | |
| 266 } | |
| 267 return samples; | |
| 268 } | |
| 269 | |
| 270 static BlockSample _execBlock(Function test, int count) { | |
| 271 Stopwatch s = new Stopwatch(); | |
| 272 s.start(); | |
| 273 for (int i = 0; i < count; i++) { | |
| 274 test(); | |
| 275 } | |
| 276 s.stop(); | |
| 277 return new BlockSample(count, s.elapsedMicroseconds * 1000); | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 /** | |
| 282 * Define CPSTest type. | |
| 283 */ | |
| 284 typedef void CPSTest(Function continuation); | |
| 285 | |
| 286 typedef void ReportHandler(TestReport r); | |
| 287 | |
| 288 /** | |
| 289 * Run non-blocking-style using Continuation Passing Style callbacks. Tests may | |
| 290 * be run a specified number of times, or they can be run based on performance | |
| 291 * to estimate a particular duration. | |
| 292 */ | |
| 293 class CPSBenchmarkRunner extends Runner { | |
| 294 | |
| 295 CPSBenchmarkRunner(): _cpsTests = []; | |
| 296 | |
| 297 void addTest(CPSTest test) { | |
| 298 _cpsTests.add(test); | |
| 299 } | |
| 300 | |
| 301 void runTests([int index = 0, Function continuation = null]) { | |
| 302 if (index < _cpsTests.length) { | |
| 303 _cpsTests[index](_(){ | |
| 304 _addToEventQueue(_() => runTests(index + 1, continuation)); | |
| 305 }); | |
| 306 } else { | |
| 307 if (null != continuation) { | |
| 308 _addToEventQueue(_() => continuation()); | |
| 309 } | |
| 310 } | |
| 311 } | |
| 312 | |
| 313 List<CPSTest> _cpsTests; | |
| 314 | |
| 315 static void runCount(String id, String desc, CountTestConfig config, | |
| 316 CPSTest test, void continuation()) { | |
| 317 if (runTest(id)) { | |
| 318 _runTests(test, config._warmup, 1, (List<BlockSample> warmupSamples){ | |
| 319 int blocksize = | |
| 320 config._blocksize <= 0 ? config._reps : config._blocksize; | |
| 321 _runTests(test, config._reps, blocksize, | |
| 322 _(List<BlockSample> resultSamples) { | |
| 323 config.reportHandler( | |
| 324 new TestReport(id, desc, warmupSamples, resultSamples)); | |
| 325 continuation(); | |
| 326 }); | |
| 327 }); | |
| 328 } else { | |
| 329 continuation(); | |
| 330 } | |
| 331 } | |
| 332 | |
| 333 static void runTimed(String id, String desc, TimedTestConfig config, | |
| 334 CPSTest test, void continuation()) { | |
| 335 if (runTest(id)) { | |
| 336 _runTests(test, config._warmup, 1, (List<BlockSample> warmupSamples){ | |
| 337 _calibrate(config._minSampleTimeMs, 5, test, (PerformanceModel model){ | |
| 338 int reps = | |
| 339 model.repsFor(1E6 * config._targetTimeMs, config._blocksize); | |
| 340 int blocksize = | |
| 341 config._blocksize <= 0 ? reps : config._blocksize; | |
| 342 _runTests(test, reps, blocksize, (List<BlockSample> results) { | |
| 343 config.reportHandler( | |
| 344 new TestReport(id, desc, warmupSamples, results)); | |
| 345 continuation(); | |
| 346 }); | |
| 347 }); | |
| 348 }); | |
| 349 } else { | |
| 350 continuation(); | |
| 351 } | |
| 352 } | |
| 353 | |
| 354 static void nextTest(Function testLoop, int iteration) { | |
| 355 _addToEventQueue(() => testLoop(iteration + 1)); | |
| 356 } | |
| 357 | |
| 358 static void _calibrate(int minSampleTimeMs, int maxAttempts, | |
| 359 CPSTest test, void continuation(PerformanceModel model)) { | |
| 360 _buildPerformanceModel(minSampleTimeMs, test, (PerformanceModel model){ | |
| 361 if (maxAttempts > 1 && !model.isValid()) { | |
| 362 _calibrate(minSampleTimeMs, maxAttempts - 1, test, continuation); | |
| 363 } else { | |
| 364 continuation(model); | |
| 365 } | |
| 366 }); | |
| 367 } | |
| 368 | |
| 369 static void _buildPerformanceModel( | |
| 370 int minSampleTimeMs, CPSTest test, void continuation(PerformanceModel m), | |
| 371 [int iterations = 1, List<BlockSample> calibrationResults = null]) { | |
| 372 List<BlockSample> _calibrationResults = | |
| 373 null == calibrationResults ? [] : calibrationResults; | |
| 374 _runTests(test, iterations, 1000, (List<BlockSample> calibration) { | |
| 375 _calibrationResults.addAll(calibration); | |
| 376 if (BlockSample._totalTime(calibration) < (1E6 * minSampleTimeMs)) { | |
| 377 _buildPerformanceModel(minSampleTimeMs, test, continuation, | |
| 378 iterations: iterations * 2, | |
| 379 calibrationResults: _calibrationResults); | |
| 380 } else { | |
| 381 PerformanceModel model = | |
| 382 new PerformanceModel.calculate(_calibrationResults); | |
| 383 continuation(model); | |
| 384 } | |
| 385 }); | |
| 386 } | |
| 387 | |
| 388 static void _runTests(CPSTest test, int reps, int blocksize, | |
| 389 void continuation(List<BlockSample> samples), | |
| 390 [List<BlockSample> samples = null]) { | |
| 391 List<BlockSample> localSamples = (null == samples) ? [] : samples; | |
| 392 if (reps > 0) { | |
| 393 int blockCount = Math.min(blocksize, reps); | |
| 394 _execBlock(test, blockCount, (BlockSample sample){ | |
| 395 localSamples.add(sample); | |
| 396 _addToEventQueue(() => | |
| 397 _runTests(test, reps - blockCount, blocksize, | |
| 398 continuation, localSamples)); | |
| 399 }); | |
| 400 } else { | |
| 401 continuation(localSamples); | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 static void _execBlock(CPSTest test, int count, | |
| 406 void continuation(BlockSample sample)) { | |
| 407 Stopwatch s = new Stopwatch(); | |
| 408 s.start(); | |
| 409 _innerLoop(test, count, () { | |
| 410 s.stop(); | |
| 411 continuation(new BlockSample(count, s.elapsedInUs() * 1000)); | |
| 412 }); | |
| 413 } | |
| 414 | |
| 415 static void _innerLoop(CPSTest test, int remainingCount, | |
| 416 Function continuation) { | |
| 417 if (remainingCount > 1) { | |
| 418 test(() => _innerLoop(test, remainingCount - 1, continuation)); | |
| 419 } else { | |
| 420 continuation(); | |
| 421 } | |
| 422 } | |
| 423 | |
| 424 static void _addToEventQueue(Function action) { | |
| 425 Timer.run(action); | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 class CountTestConfig { | |
| 430 CountTestConfig(int this._warmup, int this._reps, | |
| 431 [int blocksize = -1, ReportHandler reportHandler = null]) { | |
| 432 this._blocksize = blocksize; | |
| 433 this._reportHandler = (null == reportHandler) ? | |
| 434 _(TestReport r) => r.printReport() : reportHandler; | |
| 435 } | |
| 436 | |
| 437 Function _reportHandler; | |
| 438 Function get reportHandler => _reportHandler; | |
| 439 int _warmup; | |
| 440 int _reps; | |
| 441 int _blocksize; | |
| 442 } | |
| 443 | |
| 444 class TimedTestConfig { | |
| 445 TimedTestConfig(int this._warmup, int this._targetTimeMs, | |
| 446 [int minSampleTimeMs = 100, int blocksize = -1, | |
| 447 ReportHandler reportHandler = null]) : | |
| 448 this._minSampleTimeMs = minSampleTimeMs, | |
| 449 this._blocksize = blocksize { | |
| 450 this._reportHandler = (null == reportHandler) ? | |
| 451 _(TestReport r) => r.printReport() : reportHandler; | |
| 452 } | |
| 453 | |
| 454 Function _reportHandler; | |
| 455 Function get reportHandler => _reportHandler; | |
| 456 int _warmup; | |
| 457 int _targetTimeMs; | |
| 458 int _minSampleTimeMs; | |
| 459 int _blocksize; | |
| 460 } | |
| OLD | NEW |