| // Copyright (c) 2024, 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 'dart:io'; |
| import 'dart:math'; |
| import 'dart:typed_data'; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/binary/ast_from_binary.dart'; |
| import 'package:kernel/binary/ast_to_binary.dart'; |
| import 'package:kernel/src/printer.dart'; |
| |
| import '../tool/compile.dart' as compile; |
| |
| final String usage = ''' |
| Usage: kernel_binary_bench.dart [--golem|--raw] {--metadata|--onlyCold} <Benchmark> <SourceDill> |
| |
| Benchmark can be one of: ${benchmarks.keys.join(', ')} |
| '''; |
| |
| typedef void Benchmark(Uint8List bytes); |
| |
| final Map<String, Benchmark> 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; |
| bool metadataAware = false; |
| bool onlyCold = false; |
| |
| void main(List<String> args) async { |
| if (args.length == 1 && args[0] == "--compile") { |
| // Allow - although in practise unused - to go to a bigger target (in this |
| // case the compile target) - in order to get a more real polymorphic |
| // potential (especially for AOT compiles). |
| return await compile.main(args); |
| } |
| if (!_parseArgs(args)) { |
| print(usage); |
| exit(-1); |
| } |
| |
| final Uint8List bytes = sourceDill.readAsBytesSync(); |
| benchmark!(bytes); |
| } |
| |
| int warmupIterations = 100; |
| int benchmarkIterations = 50; |
| |
| void _benchmarkAstFromBinary(Uint8List bytes, {bool eager = true}) { |
| final String nameSuffix = eager ? 'Eager' : 'Lazy'; |
| |
| final Stopwatch sw = new Stopwatch()..start(); |
| _fromBinary(bytes, eager: eager); |
| final int coldRunUs = sw.elapsedMicroseconds; |
| sw.reset(); |
| if (onlyCold) { |
| new BenchmarkResult('AstFromBinary${nameSuffix}', coldRunUs, |
| coldRunUs.toDouble(), [coldRunUs]).report(); |
| return; |
| } |
| |
| for (int i = 0; i < warmupIterations; i++) { |
| _fromBinary(bytes, eager: eager); |
| } |
| final double warmupUs = sw.elapsedMicroseconds / warmupIterations; |
| |
| final List<int> runsUs = |
| new List<int>.filled(benchmarkIterations, /* dummy value = */ 0); |
| for (int i = 0; i < benchmarkIterations; i++) { |
| sw.reset(); |
| _fromBinary(bytes, eager: eager, verbose: i == benchmarkIterations - 1); |
| runsUs[i] = sw.elapsedMicroseconds; |
| } |
| |
| new BenchmarkResult('AstFromBinary${nameSuffix}', coldRunUs, warmupUs, runsUs) |
| .report(); |
| } |
| |
| void _benchmarkAstToBinary(Uint8List bytes) { |
| final Component p = _fromBinary(bytes, eager: true); |
| final Stopwatch sw = new Stopwatch()..start(); |
| _toBinary(p); |
| final int coldRunUs = sw.elapsedMicroseconds; |
| sw.reset(); |
| |
| for (int i = 0; i < warmupIterations; i++) { |
| _toBinary(p); |
| } |
| final double warmupUs = sw.elapsedMicroseconds / warmupIterations; |
| |
| final List<int> runsUs = |
| new List<int>.filled(benchmarkIterations, /* dummy value = */ 0); |
| for (int 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 as T; |
| |
| void report() { |
| runsUs.sort(); |
| |
| int P(int p) => runsUs[((runsUs.length - 1) * (p / 100)).ceil()]; |
| |
| final int sum = runsUs.reduce(add); |
| final double avg = sum / runsUs.length; |
| final int min = runsUs.first; |
| final int max = runsUs.last; |
| final double 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 String 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<String> argsOrg) { |
| List<String> trimmedArgs = []; |
| for (String arg in argsOrg) { |
| if (arg == "--golem") { |
| forGolem = true; |
| } else if (arg == "--raw") { |
| forRaw = true; |
| } else if (arg == "--metadata") { |
| metadataAware = true; |
| } else if (arg == "--onlyCold") { |
| onlyCold = true; |
| } else if (arg.startsWith("--warmups=")) { |
| warmupIterations = int.parse(arg.substring("--warmups=".length)); |
| } else if (arg.startsWith("--iterations=")) { |
| benchmarkIterations = int.parse(arg.substring("--iterations=".length)); |
| } else { |
| trimmedArgs.add(arg); |
| } |
| } |
| |
| if (trimmedArgs.length != 2) { |
| return false; |
| } |
| if (forGolem && forRaw) { |
| return false; |
| } |
| |
| benchmark = benchmarks[trimmedArgs[0]]; |
| if (benchmark == null) { |
| return false; |
| } |
| |
| sourceDill = new File(trimmedArgs[1]); |
| if (!sourceDill.existsSync()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Component _fromBinary(Uint8List bytes, |
| {required bool eager, bool verbose = false}) { |
| Component component = new Component(); |
| if (metadataAware) { |
| // This is currently (October 2024) what VmTarget.configureComponent does. |
| component.metadata.putIfAbsent( |
| CallSiteAttributesMetadataRepository.repositoryTag, |
| () => new CallSiteAttributesMetadataRepository()); |
| BinaryBuilderWithMetadata builder = new BinaryBuilderWithMetadata(bytes, |
| filename: 'filename', disableLazyReading: eager); |
| builder.readComponent(component); |
| if (verbose) { |
| // No current verbose output. |
| } |
| } else { |
| new BinaryBuilder(bytes, filename: 'filename', disableLazyReading: eager) |
| .readComponent(component); |
| } |
| return component; |
| } |
| |
| 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(Component p) { |
| new BinaryPrinter(new SimpleSink()).writeComponentFile(p); |
| } |
| |
| // The below is copied from package:vm so to test metadata properly without |
| // depending on package:vm. |
| |
| /// Metadata for annotating call sites with various attributes. |
| class CallSiteAttributesMetadata { |
| final DartType receiverType; |
| |
| const CallSiteAttributesMetadata({required this.receiverType}); |
| |
| @override |
| String toString() => |
| "receiverType:${receiverType.toText(astTextStrategyForTesting)}"; |
| } |
| |
| /// Repository for [CallSiteAttributesMetadata]. |
| class CallSiteAttributesMetadataRepository |
| extends MetadataRepository<CallSiteAttributesMetadata> { |
| static final repositoryTag = 'vm.call-site-attributes.metadata'; |
| |
| @override |
| final String tag = repositoryTag; |
| |
| @override |
| final Map<TreeNode, CallSiteAttributesMetadata> mapping = |
| <TreeNode, CallSiteAttributesMetadata>{}; |
| |
| @override |
| void writeToBinary( |
| CallSiteAttributesMetadata metadata, Node node, BinarySink sink) { |
| sink.writeDartType(metadata.receiverType); |
| } |
| |
| @override |
| CallSiteAttributesMetadata readFromBinary(Node node, BinarySource source) { |
| final type = source.readDartType(); |
| return new CallSiteAttributesMetadata(receiverType: type); |
| } |
| } |