-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
2 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |