Files
sdk/pkg/kernel/test/binary_bench2.dart
T
Jens Johansen 6d4ebea83b [kernel] Optimize reading of metadata further
This optimization is based on the assumption (which at least holds true
for the dill file extracted from the mentioned bug) that only very few
nodes actually have metadata associated with it. For the test file, more
than two million node-offsets is looked up, and only ~7.5 thousand nodes
actually have metadata on it.
What is done here is to assume that most often we ask offsets in order
and that most often the answer is that there's no metadata (when we fall
into this case we can return `false` quickly) and we can skip looking up
the actual metadata. This furthermore - although not benchmark here -
should make it (a lot) faster in the case where we have multiple
subsections as we here only lookup in in one index. Only upon a `true`
response from that we have to lookup in all subsections to find and read
the actual metadata to associate.

Reading the test file without metadata, taking an average of averages
(average of 5 runs of the binary benchmarks output for
"AstFromBinaryEager") takes: 374481.4 us.

Before https://dart-review.googlesource.com/c/sdk/+/391761 reading the
test file with metadata takes: 627120.68 us.
This was about a quarter of a second, or 67+% slower.

With https://dart-review.googlesource.com/c/sdk/+/391761 this number
became: 441784.44 us.
A reduction of ~185 ms, or almost 30%, making the tax of reading
metadata "only" ~18%.

With this CL this number goes to 398011.44 us.
This is a further reduction of ~43 ms, or almost 10% (for a combined
reduction of ~230 ms or 36+%).
The tax of reading metadata is now ~6.3%.

Bug: https://github.com/flutter/flutter/issues/156713
Change-Id: I20bbabecfc8976293fe897528d05fc2145e2a8c0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392021
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
2024-10-28 11:11:48 +00:00

267 lines
7.4 KiB
Dart

// 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 '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';
import 'package:kernel/src/printer.dart';
final String usage = '''
Usage: binary_bench2.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 (!_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(List<int> 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);
}
}