eb8156415c
Change-Id: Iac3850696fb5e48dff847d17f7110644e8b8bd03 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/395920 Reviewed-by: Jens Johansen <jensj@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
294 lines
9.3 KiB
Dart
294 lines
9.3 KiB
Dart
// Copyright (c) 2016, 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.
|
|
|
|
/// An entrypoint used to run portions of analyzer and measure its performance.
|
|
///
|
|
/// TODO(sigmund): rename to 'analyzer_perf.dart' in sync with changes to the
|
|
/// perf bots.
|
|
///
|
|
/// This file was started to measure the implementation of the front-end when it
|
|
/// was based on the analyzer codebase. Now that we are using cfe as the
|
|
/// implementation (which is measured in cfe_perf.dart), we still want to
|
|
/// measure the analyzer to ensure that there are no regressions when replacing
|
|
/// features (e.g. there is no regression from replacing summaries with kernel
|
|
/// outlines).
|
|
library front_end.tool.perf;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io' show Directory, File, Platform, exit;
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart';
|
|
import 'package:_fe_analyzer_shared/src/scanner/string_canonicalizer.dart';
|
|
import 'package:analyzer/dart/analysis/features.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/element/element.dart';
|
|
import 'package:analyzer/src/dart/analysis/experiments.dart';
|
|
import 'package:analyzer/error/listener.dart';
|
|
import 'package:analyzer/file_system/file_system.dart' show Folder;
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:analyzer/source/line_info.dart';
|
|
import 'package:analyzer/source/source.dart';
|
|
import 'package:analyzer/src/context/packages.dart';
|
|
import 'package:analyzer/src/dart/sdk/sdk.dart' show FolderBasedDartSdk;
|
|
import 'package:analyzer/src/file_system/file_system.dart';
|
|
import 'package:analyzer/src/generated/parser.dart';
|
|
import 'package:analyzer/src/generated/source.dart'
|
|
show DartUriResolver, SourceFactory;
|
|
import 'package:analyzer/src/source/package_map_resolver.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
Future<void> main(List<String> args) async {
|
|
// TODO(sigmund): provide sdk folder as well.
|
|
if (args.length < 2) {
|
|
print('usage: perf.dart <bench-id> <entry.dart>');
|
|
exit(1);
|
|
}
|
|
|
|
var bench = args[0];
|
|
var entryUri = Uri.base.resolve(args[1]);
|
|
|
|
setup(path.fromUri(entryUri));
|
|
|
|
final Set<Source> files = scanReachableFiles(entryUri);
|
|
final Set<Uint8List> filesAsBytes = loadFileContentsAsBytes(files);
|
|
final handlers = {
|
|
'scan': () async => scanFiles(files, tokenize),
|
|
'scan_bytes': () async => scanFiles(filesAsBytes, tokenizeBytes),
|
|
'parse': () async => parseFiles(files),
|
|
};
|
|
|
|
// Ensure we start scanning with a clean string canonicalizer cache.
|
|
clearStringCanonicalizationCache();
|
|
|
|
var handler = handlers[bench];
|
|
if (handler == null) {
|
|
print('unsupported bench-id: $bench. Please specify one of the following: '
|
|
'${handlers.keys.join(", ")}');
|
|
exit(1);
|
|
}
|
|
|
|
// TODO(sigmund): replace the warmup with instrumented snapshots.
|
|
int iterations = bench.contains('kernel_gen') ? 2 : 10;
|
|
for (int i = 0; i < iterations; i++) {
|
|
var totalTimer = new Stopwatch()..start();
|
|
print('== iteration $i');
|
|
await handler();
|
|
totalTimer.stop();
|
|
report('total', totalTimer.elapsedMicroseconds);
|
|
}
|
|
}
|
|
|
|
/// Cumulative time spent parsing.
|
|
Stopwatch parseTimer = new Stopwatch();
|
|
|
|
/// Cumulative time spent scanning.
|
|
Stopwatch scanTimer = new Stopwatch();
|
|
|
|
/// Size of all sources.
|
|
int inputSize = 0;
|
|
|
|
/// Factory to load and resolve app, packages, and sdk sources.
|
|
late SourceFactory sources;
|
|
|
|
/// Path to the root of the built SDK that is being used to execute this script.
|
|
final _sdkPath = _findSdkPath();
|
|
|
|
/// Add to [files] all sources reachable from [start].
|
|
void collectSources(Source start, Set<Source> files) {
|
|
if (!files.add(start)) return;
|
|
var unit = parseDirectives(start);
|
|
for (var directive in unit.directives) {
|
|
if (directive is UriBasedDirective) {
|
|
var next = sources.resolveUri(start, directive.uri.stringValue)!;
|
|
collectSources(next, files);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Uses the diet-parser to parse only directives in [source].
|
|
CompilationUnit parseDirectives(Source source) {
|
|
var result = tokenize(source);
|
|
var lineInfo = LineInfo(result.lineStarts);
|
|
var parser = new Parser(
|
|
source,
|
|
AnalysisErrorListener.NULL_LISTENER,
|
|
featureSet: FeatureSet.latestLanguageVersion(),
|
|
languageVersion: LibraryLanguageVersion(
|
|
package: ExperimentStatus.currentVersion, override: null),
|
|
lineInfo: lineInfo,
|
|
);
|
|
return parser.parseDirectives(result.tokens);
|
|
}
|
|
|
|
/// Parses every file in [files] and reports the time spent doing so.
|
|
void parseFiles(Set<Source> files) {
|
|
scanTimer = new Stopwatch();
|
|
parseTimer = new Stopwatch();
|
|
for (var source in files) {
|
|
parseFull(source);
|
|
}
|
|
|
|
report('scan', scanTimer.elapsedMicroseconds);
|
|
report('parse', parseTimer.elapsedMicroseconds);
|
|
}
|
|
|
|
/// Parse the full body of [source] and return it's compilation unit.
|
|
CompilationUnit parseFull(Source source) {
|
|
var result = tokenize(source);
|
|
var lineInfo = LineInfo(result.lineStarts);
|
|
parseTimer.start();
|
|
var parser = new Parser(
|
|
source,
|
|
AnalysisErrorListener.NULL_LISTENER,
|
|
featureSet: FeatureSet.latestLanguageVersion(),
|
|
languageVersion: LibraryLanguageVersion(
|
|
package: ExperimentStatus.currentVersion, override: null),
|
|
lineInfo: lineInfo,
|
|
);
|
|
var unit = parser.parseCompilationUnit(result.tokens);
|
|
parseTimer.stop();
|
|
return unit;
|
|
}
|
|
|
|
/// Report that metric [name] took [time] micro-seconds to process
|
|
/// [inputSize] characters.
|
|
void report(String name, int time) {
|
|
StringBuffer sb = new StringBuffer();
|
|
String padding = ' ' * (20 - name.length);
|
|
sb.write('$name:$padding $time us, ${time ~/ 1000} ms');
|
|
String invSpeed = (time * 1000 / inputSize).toStringAsFixed(2);
|
|
sb.write(', $invSpeed ns/char');
|
|
print('$sb');
|
|
}
|
|
|
|
/// Scans every file in [files] and reports the time spent doing so.
|
|
void scanFiles<T>(Set<T> files, void Function(T) scannerFunction) {
|
|
// `tokenize` records how many chars are scanned and how long it takes to scan
|
|
// them. As this function is called repeatedly when running as a benchmark, we
|
|
// make sure to clear the data and compute it again every time.
|
|
scanTimer = new Stopwatch();
|
|
for (var source in files) {
|
|
scannerFunction(source);
|
|
}
|
|
|
|
report('scan', scanTimer.elapsedMicroseconds);
|
|
}
|
|
|
|
/// Load and scans all files we need to process: files reachable from the
|
|
/// entrypoint and all core libraries automatically included by the VM.
|
|
Set<Source> scanReachableFiles(Uri entryUri) {
|
|
var files = new Set<Source>();
|
|
var loadTimer = new Stopwatch()..start();
|
|
scanTimer = new Stopwatch();
|
|
collectSources(sources.forUri2(entryUri)!, files);
|
|
|
|
var libs = [
|
|
'dart:async',
|
|
'dart:collection',
|
|
'dart:convert',
|
|
'dart:core',
|
|
'dart:developer',
|
|
'dart:_internal',
|
|
'dart:isolate',
|
|
'dart:math',
|
|
'dart:mirrors',
|
|
'dart:typed_data',
|
|
'dart:io'
|
|
];
|
|
|
|
for (var lib in libs) {
|
|
collectSources(sources.forUri(lib)!, files);
|
|
}
|
|
|
|
loadTimer.stop();
|
|
|
|
inputSize = 0;
|
|
for (var s in files) {
|
|
inputSize += s.contents.data.length;
|
|
}
|
|
print('input size: ${inputSize} chars');
|
|
var loadTime = loadTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds;
|
|
report('load', loadTime);
|
|
report('scan', scanTimer.elapsedMicroseconds);
|
|
return files;
|
|
}
|
|
|
|
/// Loads the file contents of all [files] as bytes.
|
|
Set<Uint8List> loadFileContentsAsBytes(Set<Source> files) {
|
|
return files.map((Source source) {
|
|
return utf8.encode(source.contents.data);
|
|
}).toSet();
|
|
}
|
|
|
|
/// Sets up analyzer to be able to load and resolve app, packages, and sdk
|
|
/// sources.
|
|
void setup(String path) {
|
|
var provider = PhysicalResourceProvider.INSTANCE;
|
|
|
|
var packages = findPackagesFrom(
|
|
provider,
|
|
provider.getResource(path),
|
|
);
|
|
|
|
var packageMap = <String, List<Folder>>{};
|
|
for (var package in packages.packages) {
|
|
packageMap[package.name] = [package.libFolder];
|
|
}
|
|
|
|
sources = new SourceFactory([
|
|
new ResourceUriResolver(provider),
|
|
new PackageMapUriResolver(provider, packageMap),
|
|
new DartUriResolver(
|
|
new FolderBasedDartSdk(provider, provider.getFolder(_sdkPath))),
|
|
]);
|
|
}
|
|
|
|
/// Scan [source] and return the first token produced by the scanner.
|
|
ScannerResult tokenize(Source source) {
|
|
scanTimer.start();
|
|
// TODO(sigmund): is there a way to scan from a random-access-file without
|
|
// first converting to String?
|
|
ScannerResult result =
|
|
scanString(source.contents.data, includeComments: false);
|
|
var token = result.tokens;
|
|
if (result.hasErrors) {
|
|
// Ignore errors.
|
|
while (token is ErrorToken) {
|
|
token = token.next!;
|
|
}
|
|
}
|
|
scanTimer.stop();
|
|
return result;
|
|
}
|
|
|
|
ScannerResult tokenizeBytes(Uint8List source) {
|
|
scanTimer.start();
|
|
ScannerResult result = scan(source, includeComments: false);
|
|
var token = result.tokens;
|
|
if (result.hasErrors) {
|
|
// Ignore errors.
|
|
while (token is ErrorToken) {
|
|
token = token.next!;
|
|
}
|
|
}
|
|
scanTimer.stop();
|
|
return result;
|
|
}
|
|
|
|
String _findSdkPath() {
|
|
var executable = Platform.resolvedExecutable;
|
|
var executableDir = path.dirname(executable);
|
|
for (var candidate in [
|
|
path.dirname(executableDir),
|
|
path.join(executableDir, 'dart-sdk')
|
|
]) {
|
|
if (new File(path.join(candidate, 'lib', 'libraries.json')).existsSync()) {
|
|
return candidate;
|
|
}
|
|
}
|
|
// Not found; guess "sdk" relative to the current directory.
|
|
return new Directory('sdk').absolute.path;
|
|
}
|