| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// A benchmark library. This library supports running benchmarks which can |
| /// run asynchronously. |
| library services.bench; |
| |
| import 'dart:async'; |
| import 'dart:convert' show json; |
| |
| abstract class Benchmark { |
| final String name; |
| |
| Benchmark(this.name); |
| |
| Future<void> init() => Future<void>.value(); |
| |
| Future<void> perform(); |
| |
| /// Called once when this benchmark will no longer be used. |
| Future<void> tearDown() => Future<void>.value(); |
| |
| @override |
| String toString() => name; |
| } |
| |
| typedef BenchmarkLogger = void Function(String str); |
| |
| class BenchmarkHarness { |
| final bool asJson; |
| final BenchmarkLogger logger; |
| |
| BenchmarkHarness({this.asJson, this.logger = print}); |
| |
| Future<void> benchmark(List<Benchmark> benchmarks) async { |
| log('Running ${benchmarks.length} benchmarks.'); |
| log(''); |
| |
| final results = <BenchMarkResult>[]; |
| |
| await Future.forEach(benchmarks, (Benchmark benchmark) => benchmark.init()); |
| |
| return Future.forEach(benchmarks, (Benchmark benchmark) { |
| return benchmarkSingle(benchmark).then((BenchMarkResult result) { |
| results.add(result); |
| }); |
| }).then((_) { |
| if (asJson) { |
| logger(json.encode(results |
| .map((BenchMarkResult r) => |
| <String, num>{r.benchmark.name: r.averageMilliseconds()}) |
| .toList())); |
| } |
| }); |
| } |
| |
| Future<BenchMarkResult> benchmarkSingle(Benchmark benchmark) { |
| return _warmup(benchmark).then((_) { |
| return _measure(benchmark); |
| }).then((BenchMarkResult result) { |
| logResult(result); |
| return result; |
| }).whenComplete(() { |
| return benchmark.tearDown().catchError((dynamic e) => null); |
| }); |
| } |
| |
| void log(String message) { |
| if (!asJson) logger(message); |
| } |
| |
| void logResult(BenchMarkResult result) { |
| if (!asJson) logger(result.toString()); |
| } |
| |
| Future<BenchMarkResult> _warmup(Benchmark benchmark) { |
| return _time(benchmark, 2, 1000); |
| } |
| |
| Future<BenchMarkResult> _measure(Benchmark benchmark) { |
| return _time(benchmark, 10, 2000, 10000); |
| } |
| |
| Future<BenchMarkResult> _time( |
| Benchmark benchmark, int minIterations, int minMillis, |
| [int maxMillis]) { |
| final result = BenchMarkResult(benchmark); |
| final timer = Stopwatch()..start(); |
| |
| return Future.doWhile(() { |
| if (result.iteration >= minIterations && |
| timer.elapsedMilliseconds >= minMillis) { |
| return false; |
| } |
| |
| if (maxMillis != null && timer.elapsedMilliseconds >= maxMillis) { |
| return false; |
| } |
| |
| result.iteration++; |
| return benchmark.perform().then((_) => true); |
| }).then((_) { |
| timer.stop(); |
| result.microseconds = timer.elapsedMicroseconds; |
| return result; |
| }); |
| } |
| } |
| |
| class BenchMarkResult { |
| final Benchmark benchmark; |
| int iteration = 0; |
| int microseconds = 0; |
| |
| BenchMarkResult(this.benchmark); |
| |
| double averageMilliseconds() => (microseconds / iteration) / 1000.0; |
| |
| @override |
| String toString() => '[${benchmark.name.padRight(26)}: ' |
| '${averageMilliseconds().toStringAsFixed(3).padLeft(8)}ms]'; |
| } |