blob: 8bc6f4fe11da40edda5f40e05b0dd40f0a9dd475 [file] [log] [blame]
// 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]';
}