Files
sdk/pkg/front_end/tool/incremental_perf.dart
T
Jens Johansen f169abcee0 [CFE] Make standard filesystem more synchronous by default; add async versions
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>
2021-02-17 10:28:00 +00:00

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');