921ec7f507
Replace the positional Scanner constructors with a single constructor that takes the input text and error reporting callback as named required arguments. Remove the unused fasta forwarding constructor, reader offset handling, and stored first token. The scanner now keeps only the input text and returns the token stream directly from tokenize. Update analyzer, analysis server, plugin, CLI, scrape, and scanner test call sites to use the named constructor arguments. Change-Id: Ieabb7b28570d4a963563fb2d1c5ef5ac0c89032a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500340 Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Paul Berry <paulberry@google.com>
252 lines
8.3 KiB
Dart
252 lines
8.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.
|
|
library;
|
|
|
|
import 'dart:io' show exit;
|
|
|
|
import 'package:analyzer/dart/analysis/features.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/dart/ast/token.dart';
|
|
import 'package:analyzer/dart/element/element.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'
|
|
show PhysicalResourceProvider;
|
|
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/analysis/experiments.dart';
|
|
import 'package:analyzer/src/dart/scanner/scanner.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:pub_semver/pub_semver.dart';
|
|
|
|
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 totalTimer = Stopwatch()..start();
|
|
|
|
var bench = args[0];
|
|
var entryUri = Uri.base.resolve(args[1]);
|
|
|
|
await setup(args[1]);
|
|
|
|
if (bench == 'scan') {
|
|
var files = scanReachableFiles(entryUri);
|
|
// TODO(sigmund): consider replacing the warmup with instrumented snapshots.
|
|
for (var i = 0; i < 10; i++) {
|
|
scanFiles(files);
|
|
}
|
|
} else if (bench == 'parse') {
|
|
var files = scanReachableFiles(entryUri);
|
|
// TODO(sigmund): consider replacing the warmup with instrumented snapshots.
|
|
for (var i = 0; i < 10; i++) {
|
|
parseFiles(files);
|
|
}
|
|
} else {
|
|
print('unsupported bench-id: $bench. Please specify "scan" or "parse"');
|
|
// TODO(sigmund): implement the remaining benchmarks.
|
|
exit(1);
|
|
}
|
|
|
|
totalTimer.stop();
|
|
report('total', totalTimer.elapsedMicroseconds);
|
|
}
|
|
|
|
/// Cumulative time spent scanning.
|
|
Stopwatch scanTimer = Stopwatch();
|
|
|
|
/// Cumulative total number of chars scanned.
|
|
int scanTotalChars = 0;
|
|
|
|
/// Factory to load and resolve app, packages, and sdk sources.
|
|
late SourceFactory sources;
|
|
|
|
/// 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 languageVersion = LibraryLanguageVersion(
|
|
package: ExperimentStatus.currentVersion,
|
|
override: result.overrideVersion,
|
|
);
|
|
var lineInfo = LineInfo(result.lineStarts);
|
|
var parser = Parser(
|
|
DiagnosticReporter(DiagnosticListener.nullListener, source),
|
|
featureSet: FeatureSet.latestLanguageVersion(),
|
|
lineInfo: lineInfo,
|
|
languageVersion: languageVersion,
|
|
);
|
|
return parser.parseDirectives(result.tokens);
|
|
}
|
|
|
|
/// Parses every file in [files] and reports the time spent doing so.
|
|
void parseFiles(Set<Source?> files) {
|
|
// The code below will record again how many chars are scanned and how long it
|
|
// takes to scan them, even though we already did so in [scanReachableFiles].
|
|
// Recording and reporting this twice is unnecessary, but we do so for now to
|
|
// validate that the results are consistent.
|
|
scanTimer = Stopwatch();
|
|
var old = scanTotalChars;
|
|
scanTotalChars = 0;
|
|
var parseTimer = Stopwatch()..start();
|
|
for (var source in files) {
|
|
parseFull(source!);
|
|
}
|
|
parseTimer.stop();
|
|
|
|
// Report size and scanning time again. See discussion above.
|
|
if (old != scanTotalChars) print('input size changed? $old chars');
|
|
report('scan', scanTimer.elapsedMicroseconds);
|
|
|
|
var pTime = parseTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds;
|
|
report('parse', pTime);
|
|
}
|
|
|
|
/// Parse the full body of [source] and return it's compilation unit.
|
|
CompilationUnit parseFull(Source source) {
|
|
var result = tokenize(source);
|
|
var languageVersion = LibraryLanguageVersion(
|
|
package: ExperimentStatus.currentVersion,
|
|
override: result.overrideVersion,
|
|
);
|
|
var lineInfo = LineInfo(result.lineStarts);
|
|
var parser = Parser(
|
|
DiagnosticReporter(DiagnosticListener.nullListener, source),
|
|
featureSet: FeatureSet.latestLanguageVersion(),
|
|
languageVersion: languageVersion,
|
|
lineInfo: lineInfo,
|
|
);
|
|
return parser.parseCompilationUnit(result.tokens);
|
|
}
|
|
|
|
/// Report that metric [name] took [time] micro-seconds to process
|
|
/// [scanTotalChars] characters.
|
|
void report(String name, int time) {
|
|
var sb = StringBuffer();
|
|
sb.write('$name: $time us, ${time ~/ 1000} ms');
|
|
sb.write(', ${scanTotalChars * 1000 ~/ time} chars/ms');
|
|
print('$sb');
|
|
}
|
|
|
|
/// Scans every file in [files] and reports the time spent doing so.
|
|
void scanFiles(Set<Source?> files) {
|
|
// The code below will record again how many chars are scanned and how long it
|
|
// takes to scan them, even though we already did so in [scanReachableFiles].
|
|
// Recording and reporting this twice is unnecessary, but we do so for now to
|
|
// validate that the results are consistent.
|
|
scanTimer = Stopwatch();
|
|
var old = scanTotalChars;
|
|
scanTotalChars = 0;
|
|
for (var source in files) {
|
|
tokenize(source!);
|
|
}
|
|
|
|
// Report size and scanning time again. See discussion above.
|
|
if (old != scanTotalChars) print('input size changed? $old chars');
|
|
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 = <Source?>{};
|
|
var loadTimer = Stopwatch()..start();
|
|
collectSources(sources.forUri2(entryUri), files);
|
|
|
|
var libs = [
|
|
'dart:async',
|
|
'dart:cli',
|
|
'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();
|
|
|
|
print('input size: $scanTotalChars chars');
|
|
var loadTime = loadTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds;
|
|
report('load', loadTime);
|
|
report('scan', scanTimer.elapsedMicroseconds);
|
|
return files;
|
|
}
|
|
|
|
/// Sets up analyzer to be able to load and resolve app, packages, and sdk
|
|
/// sources.
|
|
Future setup(String path) async {
|
|
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 = SourceFactory([
|
|
ResourceUriResolver(provider),
|
|
PackageMapUriResolver(provider, packageMap),
|
|
DartUriResolver(FolderBasedDartSdk(provider, provider.getFolder('sdk'))),
|
|
]);
|
|
}
|
|
|
|
/// Scan [source] and return the first token produced by the scanner.
|
|
ScannerResult tokenize(Source source) {
|
|
scanTimer.start();
|
|
var contents = source.contents.data;
|
|
scanTotalChars += contents.length;
|
|
// TODO(paulberry): figure out the appropriate featureSet to use here
|
|
var featureSet = FeatureSet.latestLanguageVersion();
|
|
// TODO(sigmund): is there a way to scan from a random-access-file without
|
|
// first converting to String?
|
|
var scanner = Scanner(inputText: contents, reportError: (_) {})
|
|
..configureFeatures(
|
|
featureSetForOverriding: featureSet,
|
|
featureSet: featureSet,
|
|
)
|
|
..preserveComments = false;
|
|
var token = scanner.tokenize();
|
|
scanTimer.stop();
|
|
return ScannerResult(token, scanner.lineStarts, scanner.overrideVersion);
|
|
}
|
|
|
|
class ScannerResult {
|
|
final Token tokens;
|
|
final List<int> lineStarts;
|
|
final Version? overrideVersion;
|
|
|
|
ScannerResult(this.tokens, this.lineStarts, this.overrideVersion);
|
|
}
|