// 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'; final usage = ''' Usage: binary_bench.dart [--golem|--raw] Benchmark can be one of: ${benchmarks.keys.join(', ')} '''; typedef void Benchmark(Uint8List bytes); final benchmarks = { 'AstFromBinaryEager': (Uint8List bytes) { return _benchmarkAstFromBinary(bytes, eager: true); }, 'AstFromBinaryLazy': (Uint8List bytes) { return _benchmarkAstFromBinary(bytes, eager: false); }, 'AstToBinary': (Uint8List bytes) { return _benchmarkAstToBinary(bytes); }, }; Benchmark? benchmark; late File sourceDill; bool forGolem = false; bool forRaw = false; void main(List args) async { if (!_parseArgs(args)) { print(usage); exit(-1); } final bytes = sourceDill.readAsBytesSync(); benchmark!(bytes); } 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.filled(benchmarkIterations, /* dummy value = */ 0); 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.filled(benchmarkIterations, /* dummy value = */ 0); 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 runsUs; BenchmarkResult(this.name, this.coldRunUs, this.warmupUs, this.runsUs); static T add(T x, T y) => x + y as T; void report() { runsUs.sort(); int 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}(RunTimeRaw): ${avg} us.'); print('${name}P50(RunTimeRaw): ${P(50)} us.'); print('${name}P90(RunTimeRaw): ${P(90)} us.'); } else if (forRaw) { runsUs.forEach(print); } else { 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.'); } } } bool _parseArgs(List 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); } else if (args[0] == '--raw') { if (args.length != 3) { return false; } forRaw = true; args = args.skip(1).toList(growable: false); } benchmark = benchmarks[args[0]]; if (benchmark == null) { return false; } sourceDill = new File(args[1]); if (!sourceDill.existsSync()) { return false; } return true; } Component _fromBinary(Uint8List bytes, {eager = true}) { var component = new Component(); new BinaryBuilder(bytes, filename: 'filename', disableLazyReading: eager) .readSingleFileComponent(component); return component; } class SimpleSink implements Sink> { final List> chunks = >[]; @override void add(List chunk) { chunks.add(chunk); } @override void close() {} } void _toBinary(Component p) { new BinaryPrinter(new SimpleSink()).writeComponentFile(p); }