[kernel] Add pkg/kernel/test/binary_bench.dart.
This file implements a simple benchmark for kernel serialization and
deserialization routines.
Change-Id: Ib67745a4176c2bcf5717395af86d724ffbb164fd
Reviewed-on: https://dart-review.googlesource.com/18185
Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
diff --git a/pkg/kernel/test/binary_bench.dart b/pkg/kernel/test/binary_bench.dart
new file mode 100644
index 0000000..0bde290
--- /dev/null
+++ b/pkg/kernel/test/binary_bench.dart
@@ -0,0 +1,194 @@
+// Copyright (c) 2017, 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.
+
+// This files contains methods for benchmarking Kernel binary serialization
+// and deserialization routines.
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/binary/ast_from_binary.dart';
+import 'package:kernel/binary/ast_to_binary.dart';
+
+import 'dart:io';
+import 'dart:math';
+import 'dart:typed_data';
+
+const usage = '''
+Usage: binary_bench.dart [--golem] <Benchmark> <SourceDill>
+
+Benchmark can be one of the following:
+
+* ast-to-binary
+* ast-from-binary-lazy
+* ast-from-binary-eager
+''';
+
+enum Mode { astToBinary, astFromBinaryLazy, astFromBinaryEager }
+
+Mode mode;
+File sourceDill;
+bool forGolem = false;
+
+main(List<String> args) async {
+ if (!_parseArgs(args)) {
+ print(usage);
+ exit(-1);
+ }
+
+ final bytes = sourceDill.readAsBytesSync();
+ switch (mode) {
+ case Mode.astFromBinaryLazy:
+ _benchmarkAstFromBinary(bytes, eager: false);
+ break;
+ case Mode.astFromBinaryEager:
+ _benchmarkAstFromBinary(bytes, eager: true);
+ break;
+ case Mode.astToBinary:
+ _benchmarkAstToBinary(bytes);
+ break;
+ }
+}
+
+const warmupIterations = 100;
+const benchmarkIterations = 50;
+
+void _benchmarkAstFromBinary(Uint8List bytes, {bool eager: true}) {
+ final sw = new Stopwatch()..start();
+ _fromBinary(bytes, eager: eager);
+ final coldRunUs = sw.elapsedMicroseconds;
+ sw.reset();
+
+ for (var i = 0; i < warmupIterations; i++) {
+ _fromBinary(bytes, eager: eager);
+ }
+ final warmupUs = sw.elapsedMicroseconds / warmupIterations;
+
+ final runsUs = new List<int>(benchmarkIterations);
+ for (var i = 0; i < benchmarkIterations; i++) {
+ sw.reset();
+ _fromBinary(bytes, eager: eager);
+ runsUs[i] = sw.elapsedMicroseconds;
+ }
+
+ final nameSuffix = eager ? 'Eager' : 'Lazy';
+ new BenchmarkResult('AstFromBinary${nameSuffix}', coldRunUs, warmupUs, runsUs)
+ .report();
+}
+
+void _benchmarkAstToBinary(Uint8List bytes) {
+ final p = _fromBinary(bytes, eager: true);
+ final sw = new Stopwatch()..start();
+ _toBinary(p);
+ final coldRunUs = sw.elapsedMicroseconds;
+ sw.reset();
+
+ for (var i = 0; i < warmupIterations; i++) {
+ _toBinary(p);
+ }
+ final warmupUs = sw.elapsedMicroseconds / warmupIterations;
+
+ final runsUs = new List<int>(benchmarkIterations);
+ for (var i = 0; i < benchmarkIterations; i++) {
+ sw.reset();
+ _toBinary(p);
+ runsUs[i] = sw.elapsedMicroseconds;
+ }
+
+ new BenchmarkResult('AstToBinary', coldRunUs, warmupUs, runsUs).report();
+}
+
+class BenchmarkResult {
+ final String name;
+ final int coldRunUs;
+ final double warmupUs;
+ final List<int> runsUs;
+
+ BenchmarkResult(this.name, this.coldRunUs, this.warmupUs, this.runsUs);
+
+ static T add<T extends num>(T x, T y) => x + y;
+
+ void report() {
+ runsUs.sort();
+
+ P(int p) => runsUs[((runsUs.length - 1) * (p / 100)).ceil()];
+
+ final sum = runsUs.reduce(add);
+ final avg = sum / runsUs.length;
+ final min = runsUs.first;
+ final max = runsUs.last;
+ final std =
+ sqrt(runsUs.map((v) => pow(v - avg, 2)).reduce(add) / runsUs.length);
+
+ if (!forGolem) {
+ print('${name}Cold: ${coldRunUs} us');
+ print('${name}Warmup: ${warmupUs} us');
+ print('${name}: ${avg} us.');
+ final prefix = '-' * name.length;
+ print('${prefix}> Range: ${min}...${max} us.');
+ print('${prefix}> Std Dev: ${std.toStringAsFixed(2)}');
+ print('${prefix}> 50th percentile: ${P(50)} us.');
+ print('${prefix}> 90th percentile: ${P(90)} us.');
+ } else {
+ print('${name}(RunTimeRaw): ${avg} us.');
+ print('${name}P50(RunTimeRaw): ${P(50)} us.');
+ print('${name}P90(RunTimeRaw): ${P(90)} us.');
+ }
+ }
+}
+
+bool _parseArgs(List<String> args) {
+ if (args.length != 2 && args.length != 3) {
+ return false;
+ }
+
+ if (args[0] == '--golem') {
+ if (args.length != 3) {
+ return false;
+ }
+ forGolem = true;
+ args = args.skip(1).toList(growable: false);
+ }
+
+ switch (args[0]) {
+ case 'ast-to-binary':
+ mode = Mode.astToBinary;
+ break;
+ case 'ast-from-binary-lazy':
+ mode = Mode.astFromBinaryLazy;
+ break;
+ case 'ast-from-binary-eager':
+ mode = Mode.astFromBinaryEager;
+ break;
+ default:
+ return false;
+ }
+
+ sourceDill = new File(args[1]);
+ if (!sourceDill.existsSync()) {
+ return false;
+ }
+
+ return true;
+}
+
+Program _fromBinary(List<int> bytes, {eager: true}) {
+ var program = new Program();
+ new BinaryBuilder(bytes, 'filename', eager).readSingleFileProgram(program);
+ return program;
+}
+
+class SimpleSink implements Sink<List<int>> {
+ final List<List<int>> chunks = <List<int>>[];
+
+ @override
+ void add(List<int> chunk) {
+ chunks.add(chunk);
+ }
+
+ @override
+ void close() {}
+}
+
+void _toBinary(Program p) {
+ new BinaryPrinter(new SimpleSink()).writeProgramFile(p);
+}