f169abcee0
This CL: * Makes the standard filesystem more synchronous by default. This makes reading files for normal compiles faster. * Adds a more asynchronous version of `exists` and `readAsBytes`. This potentially allows for reading files faster when in a context where the files can actually be read in parallel. The client have to make the choise though. * Skips `Uri.base.resolveUri` when the uri has a scheme. This makes it slightly faster in my tests, but does not remove any `.` and `..`. Having those when having a scheme seems sort of weird though. If this turns out to be a problem we can add it back. * Re-orders what the standard filesystem checks in `exists` to assume files which seems more likely to happen. This - for files - means less checks and should make checking for existance faster. (This was noticed by Siggi). Using the same measurement procedure as in https://dart-review.googlesource.com/c/sdk/+/85442 timing how long fasta spends on *reading* files when compiling dart2js (when compiling via the VM) I get a good speedup. Note that the measurements are in microseconds: Before this CL (run like `out/ReleaseX64/dart pkg/compiler/bin/dart2js.dart 2>&1 | grep "Read file (total)" | tail -n 1` 10 times): Read file (total): 94678 Read file (total): 94232 Read file (total): 91726 Read file (total): 77472 Read file (total): 95582 Read file (total): 88113 Read file (total): 89245 Read file (total): 91575 Read file (total): 96291 Read file (total): 95730 With this CL (run like `out/ReleaseX64/dart pkg/compiler/bin/dart2js.dart 2>&1 | grep "Read file (total)" | tail -n 1` 10 times): Read file (total): 68379 Read file (total): 69320 Read file (total): 72930 Read file (total): 69692 Read file (total): 68685 Read file (total): 73548 Read file (total): 64649 Read file (total): 71951 Read file (total): 73486 Read file (total): 70621 Difference at 95.0% confidence -21138.3 +/- 4193 -23.111% +/- 4.5843% (Student's t, pooled s = 4462.56) Furthermore, using an internal benchmark (discussed in an email thread) I get these numbers (note that the measurements are in milliseconds): before: 4453 4249 4187 4190 4216 after: 2310 2379 2449 2467 2407 Difference at 95.0% confidence -1856.6 +/- 131.443 -43.5924% +/- 3.08625% (Student's t, pooled s = 90.1257) Using the asynchronous versions of exists and readAsBytes and checking for existance and reading in parallel on the same internal benchmark it gets to 1137 1185 1067 1089 1189 For completion, comparing to using "raw" `File` (again using the internal benchmark): sequential standard 2377 2419 2485 2373 2431 sequential io_sync 2279 2182 2237 2312 2324 io_sync faster: -150.2 +/- 76.3122 -6.21432% +/- 3.15731% (Student's t, pooled s = 52.3245) sequential standardasync 4865 4915 4753 5185 4760 sequential io_async 4746 4834 4989 4840 4891 No difference proven at 95.0% confidence parallel standard 2526 2563 2652 2616 2685 parallel io_sync 2335 2184 2244 2341 2401 io_async faster Difference at 95.0% confidence -307.4 +/- 111.037 -11.785% +/- 4.25691% (Student's t, pooled s = 76.1341) parallel standardasync 1137 1185 1067 1089 1189 parallel io_async 1105 1088 1145 1048 1113 No difference proven at 95.0% confidence TEST=Existing tests. Change-Id: I8ba56ab0768df8672bcdb693782d3f1eec86b683 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/185101 Commit-Queue: Jens Johansen <jensj@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Gary Roumanis <grouma@google.com>
323 lines
11 KiB
Dart
323 lines
11 KiB
Dart
// Copyright (c) 2017, 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.
|
|
|
|
// @dart=2.9
|
|
|
|
/// An entrypoint used to measure performance of the incremental compiler.
|
|
///
|
|
/// Given an input a program and a .json file describing edits, this script will
|
|
/// first compile the program, then apply edits, recompile the
|
|
/// program, and report relevant metrics.
|
|
///
|
|
/// The edits are encoded as a JSON array:
|
|
/// - Each entry in the array is an iteration of edits and holds a list of
|
|
/// individual edits. All changes in one iteration are applied at once
|
|
/// before calling [IncrementalKernelGenerator.computeDelta].
|
|
///
|
|
/// - Each edit is a triple declaring a string replacement operation:
|
|
/// [uri, from, to]
|
|
///
|
|
/// Edits are applied in order, so more than on edit is allowed on the same
|
|
/// file.
|
|
///
|
|
/// For example:
|
|
/// [
|
|
/// {
|
|
/// "name" : "big_change",
|
|
/// "edits" : [
|
|
/// ["input1.dart", "black", "green"],
|
|
/// ["input1.dart", "30px", "10px"],
|
|
/// ["input2.dart", "a.toString()", ""$a""]
|
|
/// ]
|
|
/// },
|
|
/// {
|
|
/// "name" : "small_change",
|
|
/// "edits" : [
|
|
/// ["input1.dart", "green", "blue"]
|
|
/// ]
|
|
/// }
|
|
/// ]
|
|
///
|
|
/// Is interpreted as 2 iterations, the first iteration updates input1.dart
|
|
/// with 2 changes, and input2.dart with one change. The second iteration
|
|
/// updates input1.dart a second time.
|
|
library front_end.tool.incremental_perf;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io' hide FileSystemEntity;
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:front_end/src/api_prototype/file_system.dart'
|
|
show FileSystemEntity;
|
|
import 'package:front_end/src/api_prototype/front_end.dart';
|
|
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
|
|
import 'package:front_end/src/api_prototype/memory_file_system.dart';
|
|
import 'package:front_end/src/api_prototype/standard_file_system.dart';
|
|
import 'package:front_end/src/base/processed_options.dart';
|
|
import 'package:front_end/src/fasta/uri_translator.dart';
|
|
|
|
import 'perf_common.dart';
|
|
|
|
main(List<String> args) async {
|
|
var options = argParser.parse(args);
|
|
if (options.rest.length != 2) {
|
|
throw """
|
|
usage: incremental_perf.dart [options] <entry.dart> <edits.json>
|
|
${argParser.usage}""";
|
|
}
|
|
|
|
var entryUri = _resolveOverlayUri(options.rest[0]);
|
|
var editsUri = Uri.base.resolve(options.rest[1]);
|
|
var changeSets =
|
|
parse(jsonDecode(new File.fromUri(editsUri).readAsStringSync()));
|
|
bool verbose = options["verbose"];
|
|
bool verboseCompilation = options["verbose-compilation"];
|
|
bool isFlutter = options["target"] == "flutter";
|
|
bool useMinimalGenerator = options["implementation"] == "minimal";
|
|
TimingsCollector collector = new TimingsCollector(verbose);
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
await benchmark(
|
|
collector,
|
|
entryUri,
|
|
isFlutter,
|
|
useMinimalGenerator,
|
|
verbose,
|
|
verboseCompilation,
|
|
changeSets,
|
|
options["sdk-summary"],
|
|
options["sdk-library-specification"],
|
|
options["cache"]);
|
|
if (!options["loop"]) break;
|
|
}
|
|
collector.printTimings();
|
|
}
|
|
|
|
Future benchmark(
|
|
TimingsCollector collector,
|
|
Uri entryUri,
|
|
bool isFlutter,
|
|
bool useMinimalGenerator,
|
|
bool verbose,
|
|
bool verboseCompilation,
|
|
List<ChangeSet> changeSets,
|
|
String sdkSummary,
|
|
String sdkLibrarySpecification,
|
|
String cache) async {
|
|
var overlayFs = new OverlayFileSystem();
|
|
var compilerOptions = new CompilerOptions()
|
|
..verbose = verboseCompilation
|
|
..fileSystem = overlayFs
|
|
..onDiagnostic = onDiagnosticMessageHandler()
|
|
..target = createTarget(isFlutter: isFlutter)
|
|
..environmentDefines = const {};
|
|
if (sdkSummary != null) {
|
|
compilerOptions.sdkSummary = _resolveOverlayUri(sdkSummary);
|
|
}
|
|
if (sdkLibrarySpecification != null) {
|
|
compilerOptions.librariesSpecificationUri =
|
|
_resolveOverlayUri(sdkLibrarySpecification);
|
|
}
|
|
|
|
var dir = Directory.systemTemp.createTempSync("ikg-cache");
|
|
|
|
final processedOptions =
|
|
new ProcessedOptions(options: compilerOptions, inputs: [entryUri]);
|
|
final UriTranslator uriTranslator = await processedOptions.getUriTranslator();
|
|
|
|
collector.start("Initial compilation");
|
|
var generator = new IncrementalKernelGenerator(compilerOptions, entryUri);
|
|
|
|
var component = await generator.computeDelta();
|
|
collector.stop("Initial compilation");
|
|
if (verbose) {
|
|
print("Libraries changed: ${component.libraries.length}");
|
|
}
|
|
if (component.libraries.length < 1) {
|
|
throw "No libraries were changed";
|
|
}
|
|
|
|
for (final ChangeSet changeSet in changeSets) {
|
|
String name = "Change '${changeSet.name}' - Incremental compilation";
|
|
await applyEdits(
|
|
changeSet.edits, overlayFs, generator, uriTranslator, verbose);
|
|
collector.start(name);
|
|
component = await generator.computeDelta();
|
|
collector.stop(name);
|
|
if (verbose) {
|
|
print("Change '${changeSet.name}' - "
|
|
"Libraries changed: ${component.libraries.length}");
|
|
}
|
|
if (component.libraries.length < 1) {
|
|
throw "No libraries were changed";
|
|
}
|
|
}
|
|
|
|
dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
/// Apply all edits of a single iteration by updating the copy of the file in
|
|
/// the memory file system.
|
|
applyEdits(
|
|
List<Edit> edits,
|
|
OverlayFileSystem fs,
|
|
IncrementalKernelGenerator generator,
|
|
UriTranslator uriTranslator,
|
|
bool verbose) async {
|
|
for (var edit in edits) {
|
|
if (verbose) {
|
|
print('edit $edit');
|
|
}
|
|
var uri = edit.uri;
|
|
if (uri.scheme == 'package') uri = uriTranslator.translate(uri);
|
|
generator.invalidate(uri);
|
|
OverlayFileSystemEntity entity = fs.entityForUri(uri);
|
|
var contents = await entity.readAsString();
|
|
entity.writeAsStringSync(
|
|
contents.replaceAll(edit.original, edit.replacement));
|
|
}
|
|
}
|
|
|
|
/// Parse a set of edits from a JSON array. See library comment above for
|
|
/// details on the format.
|
|
List<ChangeSet> parse(List json) {
|
|
final changeSets = <ChangeSet>[];
|
|
for (final Map jsonChangeSet in json) {
|
|
final edits = <Edit>[];
|
|
for (final jsonEdit in jsonChangeSet['edits']) {
|
|
edits.add(new Edit(jsonEdit[0], jsonEdit[1], jsonEdit[2]));
|
|
}
|
|
changeSets.add(new ChangeSet(jsonChangeSet['name'], edits));
|
|
}
|
|
return changeSets;
|
|
}
|
|
|
|
/// An overlay file system that reads the original contents from the physical
|
|
/// file system, but performs updates to those files in memory.
|
|
///
|
|
/// All files in this file system use a custom URI of the form:
|
|
///
|
|
/// org-dartlang-overlay:///path/to/file.dart
|
|
///
|
|
/// This special scheme is mainly used to make it clear that the file belongs to
|
|
/// this file system and may not correspond to the contents on disk. However,
|
|
/// when the file is read for the first time, it will be retrieved from the
|
|
/// underlying file system by using the corresponding `file:*` URI:
|
|
///
|
|
/// file:///path/to/file.dart
|
|
class OverlayFileSystem implements FileSystem {
|
|
final MemoryFileSystem memory =
|
|
new MemoryFileSystem(Uri.parse('org-dartlang-overlay:///'));
|
|
final StandardFileSystem physical = StandardFileSystem.instance;
|
|
|
|
@override
|
|
FileSystemEntity entityForUri(Uri uri) {
|
|
if (uri.scheme == 'org-dartlang-overlay') {
|
|
return new OverlayFileSystemEntity(uri, this);
|
|
} else if (uri.scheme == 'file') {
|
|
// The IKG compiler reads ".packages" which might contain absolute file
|
|
// URIs (which it will then try to use on the FS). We therefore replace
|
|
// them with overlay-fs URIs as usual.
|
|
return new OverlayFileSystemEntity(_resolveOverlayUri('$uri'), this);
|
|
} else {
|
|
throw "Unsupported scheme: ${uri.scheme}."
|
|
" The OverlayFileSystem only accepts URIs"
|
|
" with the 'org-dartlang-overlay' scheme";
|
|
}
|
|
}
|
|
}
|
|
|
|
class OverlayFileSystemEntity implements FileSystemEntity {
|
|
final Uri uri;
|
|
FileSystemEntity _delegate;
|
|
final OverlayFileSystem _fs;
|
|
|
|
OverlayFileSystemEntity(this.uri, this._fs);
|
|
|
|
Future<FileSystemEntity> get delegate async {
|
|
if (_delegate != null) return _delegate;
|
|
FileSystemEntity entity = _fs.memory.entityForUri(uri);
|
|
if (await entity.exists()) {
|
|
_delegate = entity;
|
|
return _delegate;
|
|
}
|
|
return _delegate = _fs.physical.entityForUri(uri.replace(scheme: 'file'));
|
|
}
|
|
|
|
@override
|
|
Future<bool> exists() async => (await delegate).exists();
|
|
|
|
@override
|
|
Future<bool> existsAsyncIfPossible() async =>
|
|
(await delegate).existsAsyncIfPossible();
|
|
|
|
@override
|
|
Future<List<int>> readAsBytes() async => (await delegate).readAsBytes();
|
|
|
|
@override
|
|
Future<List<int>> readAsBytesAsyncIfPossible() async =>
|
|
(await delegate).readAsBytesAsyncIfPossible();
|
|
|
|
@override
|
|
Future<String> readAsString() async => (await delegate).readAsString();
|
|
|
|
void writeAsStringSync(String contents) =>
|
|
_fs.memory.entityForUri(uri).writeAsStringSync(contents);
|
|
}
|
|
|
|
/// A string replacement edit in a source file.
|
|
class Edit {
|
|
final Uri uri;
|
|
final String original;
|
|
final String replacement;
|
|
|
|
Edit(String uriString, this.original, this.replacement)
|
|
: uri = _resolveOverlayUri(uriString);
|
|
|
|
String toString() => 'Edit($uri, "$original" -> "$replacement")';
|
|
}
|
|
|
|
/// A named set of changes applied together.
|
|
class ChangeSet {
|
|
final String name;
|
|
final List<Edit> edits;
|
|
|
|
ChangeSet(this.name, this.edits);
|
|
|
|
String toString() => 'ChangeSet($name, $edits)';
|
|
}
|
|
|
|
_resolveOverlayUri(String uriString) {
|
|
Uri result = Uri.base.resolve(uriString);
|
|
return result.isScheme("file")
|
|
? result.replace(scheme: 'org-dartlang-overlay')
|
|
: result;
|
|
}
|
|
|
|
ArgParser argParser = new ArgParser()
|
|
..addFlag('verbose-compilation',
|
|
help: 'make the compiler verbose', defaultsTo: false)
|
|
..addFlag('verbose', help: 'print additional information', defaultsTo: false)
|
|
..addFlag('loop', help: 'run benchmark 8 times', defaultsTo: true)
|
|
..addOption('target',
|
|
help: 'target platform', defaultsTo: 'vm', allowed: ['vm', 'flutter'])
|
|
..addOption('cache',
|
|
help: 'caching policy used by the compiler',
|
|
defaultsTo: 'protected',
|
|
allowed: ['evicting', 'memory', 'protected'])
|
|
// TODO(johnniwinther): Remove mode option. Legacy mode is no longer
|
|
// supported.
|
|
..addOption('mode',
|
|
help: 'whether to run in strong or legacy mode',
|
|
defaultsTo: 'strong',
|
|
allowed: ['legacy', 'strong'])
|
|
..addOption('implementation',
|
|
help: 'incremental compiler implementation to use',
|
|
defaultsTo: 'default',
|
|
allowed: ['default', 'minimal'])
|
|
..addOption('sdk-summary', help: 'Location of the sdk outline.dill file')
|
|
..addOption('sdk-library-specification',
|
|
help: 'Location of the '
|
|
'sdk/lib/libraries.json file');
|