Skip to content

Commit

Permalink
[kernel] Read metadata faster
Browse files Browse the repository at this point in the history
On a flutter dill extracted via the mentioned bug I get these timings
when (once, i.e. I haven't done extra statistics) running

```
out/ReleaseX64/dart pkg/kernel/test/binary_bench2.dart --warmups=10 \
--iterations=5 --metadata AstFromBinaryEager <dill from flutter>
```
(and without `--metadata` for without metadata):

Without reading metadata: ~378 ms
With reading metadata before this CL: ~627 ms
With reading metadata with this CL: ~435 ms

Bug: flutter/flutter#156713
Change-Id: Id6cb27bc00526ff61c48eeb66ebb86dff1b971a2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391761
Commit-Queue: Jens Johansen <[email protected]>
Reviewed-by: Johnni Winther <[email protected]>
  • Loading branch information
jensjoha authored and Commit Queue committed Oct 28, 2024
1 parent 155a64c commit cb7aee5
Show file tree
Hide file tree
Showing 2 changed files with 271 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pkg/kernel/lib/binary/ast_from_binary.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4177,6 +4177,10 @@ class BinaryBuilderWithMetadata extends BinaryBuilder implements BinarySource {
@override
void _readMetadataMappings(
Component component, int binaryOffsetForMetadataPayloads) {
// If reading a component with several sub-components there's no reason to
// lookup in old ones.
_subsections = null;

// At the beginning of this function _byteOffset points right past
// metadataMappings to string table.

Expand Down
267 changes: 267 additions & 0 deletions pkg/kernel/test/binary_bench2.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
// 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) {
// print("#lookups: ${builder.lookups}");
// print("#good lookups: ${builder.goodLookups}");
}
} 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);
}
}

0 comments on commit cb7aee5

Please sign in to comment.