Skip to content

Commit cb7aee5

Browse files
jensjohaCommit Queue
authored and
Commit Queue
committed
[kernel] Read metadata faster
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]>
1 parent 155a64c commit cb7aee5

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed

pkg/kernel/lib/binary/ast_from_binary.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4177,6 +4177,10 @@ class BinaryBuilderWithMetadata extends BinaryBuilder implements BinarySource {
41774177
@override
41784178
void _readMetadataMappings(
41794179
Component component, int binaryOffsetForMetadataPayloads) {
4180+
// If reading a component with several sub-components there's no reason to
4181+
// lookup in old ones.
4182+
_subsections = null;
4183+
41804184
// At the beginning of this function _byteOffset points right past
41814185
// metadataMappings to string table.
41824186

pkg/kernel/test/binary_bench2.dart

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This files contains methods for benchmarking Kernel binary serialization
6+
// and deserialization routines.
7+
8+
import 'package:kernel/ast.dart';
9+
import 'package:kernel/binary/ast_from_binary.dart';
10+
import 'package:kernel/binary/ast_to_binary.dart';
11+
12+
import 'dart:io';
13+
import 'dart:math';
14+
import 'dart:typed_data';
15+
16+
import 'package:kernel/src/printer.dart';
17+
18+
final String usage = '''
19+
Usage: binary_bench2.dart [--golem|--raw] {--metadata|--onlyCold}<Benchmark> <SourceDill>
20+
21+
Benchmark can be one of: ${benchmarks.keys.join(', ')}
22+
''';
23+
24+
typedef void Benchmark(Uint8List bytes);
25+
26+
final Map<String, Benchmark> benchmarks = {
27+
'AstFromBinaryEager': (Uint8List bytes) {
28+
return _benchmarkAstFromBinary(bytes, eager: true);
29+
},
30+
'AstFromBinaryLazy': (Uint8List bytes) {
31+
return _benchmarkAstFromBinary(bytes, eager: false);
32+
},
33+
'AstToBinary': (Uint8List bytes) {
34+
return _benchmarkAstToBinary(bytes);
35+
},
36+
};
37+
38+
Benchmark? benchmark;
39+
late File sourceDill;
40+
bool forGolem = false;
41+
bool forRaw = false;
42+
bool metadataAware = false;
43+
bool onlyCold = false;
44+
45+
void main(List<String> args) async {
46+
if (!_parseArgs(args)) {
47+
print(usage);
48+
exit(-1);
49+
}
50+
51+
final Uint8List bytes = sourceDill.readAsBytesSync();
52+
benchmark!(bytes);
53+
}
54+
55+
int warmupIterations = 100;
56+
int benchmarkIterations = 50;
57+
58+
void _benchmarkAstFromBinary(Uint8List bytes, {bool eager = true}) {
59+
final String nameSuffix = eager ? 'Eager' : 'Lazy';
60+
61+
final Stopwatch sw = new Stopwatch()..start();
62+
_fromBinary(bytes, eager: eager);
63+
final int coldRunUs = sw.elapsedMicroseconds;
64+
sw.reset();
65+
if (onlyCold) {
66+
new BenchmarkResult('AstFromBinary${nameSuffix}', coldRunUs,
67+
coldRunUs.toDouble(), [coldRunUs]).report();
68+
return;
69+
}
70+
71+
for (int i = 0; i < warmupIterations; i++) {
72+
_fromBinary(bytes, eager: eager);
73+
}
74+
final double warmupUs = sw.elapsedMicroseconds / warmupIterations;
75+
76+
final List<int> runsUs =
77+
new List<int>.filled(benchmarkIterations, /* dummy value = */ 0);
78+
for (int i = 0; i < benchmarkIterations; i++) {
79+
sw.reset();
80+
_fromBinary(bytes, eager: eager, verbose: i == benchmarkIterations - 1);
81+
runsUs[i] = sw.elapsedMicroseconds;
82+
}
83+
84+
new BenchmarkResult('AstFromBinary${nameSuffix}', coldRunUs, warmupUs, runsUs)
85+
.report();
86+
}
87+
88+
void _benchmarkAstToBinary(Uint8List bytes) {
89+
final Component p = _fromBinary(bytes, eager: true);
90+
final Stopwatch sw = new Stopwatch()..start();
91+
_toBinary(p);
92+
final int coldRunUs = sw.elapsedMicroseconds;
93+
sw.reset();
94+
95+
for (int i = 0; i < warmupIterations; i++) {
96+
_toBinary(p);
97+
}
98+
final double warmupUs = sw.elapsedMicroseconds / warmupIterations;
99+
100+
final List<int> runsUs =
101+
new List<int>.filled(benchmarkIterations, /* dummy value = */ 0);
102+
for (int i = 0; i < benchmarkIterations; i++) {
103+
sw.reset();
104+
_toBinary(p);
105+
runsUs[i] = sw.elapsedMicroseconds;
106+
}
107+
108+
new BenchmarkResult('AstToBinary', coldRunUs, warmupUs, runsUs).report();
109+
}
110+
111+
class BenchmarkResult {
112+
final String name;
113+
final int coldRunUs;
114+
final double warmupUs;
115+
final List<int> runsUs;
116+
117+
BenchmarkResult(this.name, this.coldRunUs, this.warmupUs, this.runsUs);
118+
119+
static T add<T extends num>(T x, T y) => x + y as T;
120+
121+
void report() {
122+
runsUs.sort();
123+
124+
int P(int p) => runsUs[((runsUs.length - 1) * (p / 100)).ceil()];
125+
126+
final int sum = runsUs.reduce(add);
127+
final double avg = sum / runsUs.length;
128+
final int min = runsUs.first;
129+
final int max = runsUs.last;
130+
final double std =
131+
sqrt(runsUs.map((v) => pow(v - avg, 2)).reduce(add) / runsUs.length);
132+
133+
if (forGolem) {
134+
print('${name}(RunTimeRaw): ${avg} us.');
135+
print('${name}P50(RunTimeRaw): ${P(50)} us.');
136+
print('${name}P90(RunTimeRaw): ${P(90)} us.');
137+
} else if (forRaw) {
138+
runsUs.forEach(print);
139+
} else {
140+
print('${name}Cold: ${coldRunUs} us');
141+
print('${name}Warmup: ${warmupUs} us');
142+
print('${name}: ${avg} us.');
143+
final String prefix = '-' * name.length;
144+
print('${prefix}> Range: ${min}...${max} us.');
145+
print('${prefix}> Std Dev: ${std.toStringAsFixed(2)}');
146+
print('${prefix}> 50th percentile: ${P(50)} us.');
147+
print('${prefix}> 90th percentile: ${P(90)} us.');
148+
}
149+
}
150+
}
151+
152+
bool _parseArgs(List<String> argsOrg) {
153+
List<String> trimmedArgs = [];
154+
for (String arg in argsOrg) {
155+
if (arg == "--golem") {
156+
forGolem = true;
157+
} else if (arg == "--raw") {
158+
forRaw = true;
159+
} else if (arg == "--metadata") {
160+
metadataAware = true;
161+
} else if (arg == "--onlyCold") {
162+
onlyCold = true;
163+
} else if (arg.startsWith("--warmups=")) {
164+
warmupIterations = int.parse(arg.substring("--warmups=".length));
165+
} else if (arg.startsWith("--iterations=")) {
166+
benchmarkIterations = int.parse(arg.substring("--iterations=".length));
167+
} else {
168+
trimmedArgs.add(arg);
169+
}
170+
}
171+
172+
if (trimmedArgs.length != 2) {
173+
return false;
174+
}
175+
if (forGolem && forRaw) {
176+
return false;
177+
}
178+
179+
benchmark = benchmarks[trimmedArgs[0]];
180+
if (benchmark == null) {
181+
return false;
182+
}
183+
184+
sourceDill = new File(trimmedArgs[1]);
185+
if (!sourceDill.existsSync()) {
186+
return false;
187+
}
188+
189+
return true;
190+
}
191+
192+
Component _fromBinary(List<int> bytes,
193+
{required bool eager, bool verbose = false}) {
194+
Component component = new Component();
195+
if (metadataAware) {
196+
// This is currently (October 2024) what VmTarget.configureComponent does.
197+
component.metadata.putIfAbsent(
198+
CallSiteAttributesMetadataRepository.repositoryTag,
199+
() => new CallSiteAttributesMetadataRepository());
200+
BinaryBuilderWithMetadata builder = new BinaryBuilderWithMetadata(bytes,
201+
filename: 'filename', disableLazyReading: eager);
202+
builder.readComponent(component);
203+
if (verbose) {
204+
// print("#lookups: ${builder.lookups}");
205+
// print("#good lookups: ${builder.goodLookups}");
206+
}
207+
} else {
208+
new BinaryBuilder(bytes, filename: 'filename', disableLazyReading: eager)
209+
.readComponent(component);
210+
}
211+
return component;
212+
}
213+
214+
class SimpleSink implements Sink<List<int>> {
215+
final List<List<int>> chunks = <List<int>>[];
216+
217+
@override
218+
void add(List<int> chunk) {
219+
chunks.add(chunk);
220+
}
221+
222+
@override
223+
void close() {}
224+
}
225+
226+
void _toBinary(Component p) {
227+
new BinaryPrinter(new SimpleSink()).writeComponentFile(p);
228+
}
229+
230+
// The below is copied from package:vm so to test metadata properly without
231+
// depending on package:vm.
232+
233+
/// Metadata for annotating call sites with various attributes.
234+
class CallSiteAttributesMetadata {
235+
final DartType receiverType;
236+
237+
const CallSiteAttributesMetadata({required this.receiverType});
238+
239+
@override
240+
String toString() =>
241+
"receiverType:${receiverType.toText(astTextStrategyForTesting)}";
242+
}
243+
244+
/// Repository for [CallSiteAttributesMetadata].
245+
class CallSiteAttributesMetadataRepository
246+
extends MetadataRepository<CallSiteAttributesMetadata> {
247+
static final repositoryTag = 'vm.call-site-attributes.metadata';
248+
249+
@override
250+
final String tag = repositoryTag;
251+
252+
@override
253+
final Map<TreeNode, CallSiteAttributesMetadata> mapping =
254+
<TreeNode, CallSiteAttributesMetadata>{};
255+
256+
@override
257+
void writeToBinary(
258+
CallSiteAttributesMetadata metadata, Node node, BinarySink sink) {
259+
sink.writeDartType(metadata.receiverType);
260+
}
261+
262+
@override
263+
CallSiteAttributesMetadata readFromBinary(Node node, BinarySource source) {
264+
final type = source.readDartType();
265+
return new CallSiteAttributesMetadata(receiverType: type);
266+
}
267+
}

0 commit comments

Comments
 (0)