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