Chromium Code Reviews| Index: pkg/compiler/tool/perf.dart |
| diff --git a/pkg/compiler/tool/perf.dart b/pkg/compiler/tool/perf.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ab6941184004a41af660da7f4e62569a98a99711 |
| --- /dev/null |
| +++ b/pkg/compiler/tool/perf.dart |
| @@ -0,0 +1,322 @@ |
| +// 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 dart2js and measure its performance. |
| +library compiler.tool.perf; |
| + |
| +import 'dart:io' show exit; |
|
Harry Terkelsen
2016/10/10 20:05:07
I remember we had some problems with exit and usin
Siggi Cherem (dart-lang)
2016/10/10 21:46:13
Seems to still be an issue, but in this case it's
|
| + |
| +import 'dart:async'; |
| +import 'dart:io'; |
|
Harry Terkelsen
2016/10/10 20:05:07
this makes the previous import redundant
Siggi Cherem (dart-lang)
2016/10/10 21:46:13
Done.
|
| + |
| +import 'package:compiler/src/diagnostics/messages.dart' |
| + show Message, MessageTemplate; |
| +import 'package:compiler/src/diagnostics/diagnostic_listener.dart'; |
| +import 'package:compiler/src/io/source_file.dart'; |
| +import 'package:compiler/src/options.dart'; |
| +import 'package:compiler/src/platform_configuration.dart' as platform; |
| +import 'package:compiler/src/source_file_provider.dart'; |
| +import 'package:package_config/discovery.dart' show findPackages; |
| +import 'package:package_config/packages.dart' show Packages; |
| +import 'package:package_config/src/util.dart' show checkValidPackageUri; |
| + |
|
Harry Terkelsen
2016/10/10 20:05:07
sort these imports?
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Done.
|
| +import 'package:compiler/compiler_new.dart'; |
| +import 'package:compiler/src/common.dart'; |
| +import 'package:compiler/src/options.dart' show ParserOptions; |
| +import 'package:compiler/src/tokens/token.dart' show Token; |
| +import 'package:compiler/src/parser/element_listener.dart' show ScannerOptions; |
| +import 'package:compiler/src/parser/listener.dart'; |
| +import 'package:compiler/src/parser/partial_parser.dart'; |
| +import 'package:compiler/src/scanner/scanner.dart'; |
| +import 'package:compiler/src/parser/node_listener.dart' show NodeListener; |
| +import 'package:compiler/src/parser/parser.dart' show Parser; |
| + |
| +/// Cummulative total number of chars scanned. |
|
Harry Terkelsen
2016/10/10 20:05:07
Cumulative
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Done.
|
| +int scanTotalChars = 0; |
| + |
| +/// Cummulative time spent scanning. |
|
Harry Terkelsen
2016/10/10 20:05:07
same
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Done.
|
| +Stopwatch scanTimer = new Stopwatch(); |
| + |
| +/// Helper class used to load source files using dart2js's internal APIs. |
| +_Loader loader; |
| + |
| +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 = new Stopwatch()..start(); |
| + |
| + var bench = args[0]; |
| + var entryUri = Uri.base.resolve(args[1]); |
| + |
| + await setup(entryUri); |
| + |
| + if (bench == 'scan') { |
| + Set<SourceFile> files = await scanReachableFiles(entryUri); |
| + // TODO(sigmund): consider replacing the warmup with instrumented snapshots. |
| + for (int i = 0; i < 10; i++) scanFiles(files); |
| + } else if (bench == 'parse') { |
| + Set<SourceFile> files = await scanReachableFiles(entryUri); |
| + // TODO(sigmund): consider replacing the warmup with instrumented snapshots. |
| + for (int 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); |
| +} |
| + |
| +Future setup(Uri entryUri) async { |
| + var inputProvider = new CompilerSourceFileProvider(); |
| + var sdkLibraries = await platform.load(_platformConfigUri, inputProvider); |
| + var packages = await findPackages(entryUri); |
| + loader = new _Loader(inputProvider, sdkLibraries, packages); |
| +} |
| + |
| +/// Load and scans all files we need to process: files reachable from the |
| +/// entrypoint and all core libraries automatically included by the VM. |
| +Future<Set<SourceFile>> scanReachableFiles(Uri entryUri) async { |
| + var files = new Set<SourceFile>(); |
| + var loadTimer = new Stopwatch()..start(); |
| + var entrypoints = [ |
| + entryUri, |
| + Uri.parse("dart:async"), |
| + Uri.parse("dart:collection"), |
| + Uri.parse("dart:convert"), |
| + Uri.parse("dart:core"), |
| + Uri.parse("dart:developer"), |
| + Uri.parse("dart:_internal"), |
| + Uri.parse("dart:io"), |
| + Uri.parse("dart:isolate"), |
| + Uri.parse("dart:math"), |
| + Uri.parse("dart:mirrors"), |
| + Uri.parse("dart:typed_data"), |
| + ]; |
| + for (var entry in entrypoints) { |
| + await collectSources(await loader.loadFile(entry), files); |
| + } |
| + loadTimer.stop(); |
| + |
| + print('input size: ${scanTotalChars} chars'); |
| + var loadTime = loadTimer.elapsedMicroseconds - scanTimer.elapsedMicroseconds; |
| + report("load", loadTime); |
| + report("scan", scanTimer.elapsedMicroseconds); |
| + return files; |
| +} |
| + |
| +/// Scans every file in [files] and reports the time spent doing so. |
| +void scanFiles(Set<SourceFile> files) { |
| + scanTimer = new Stopwatch(); |
| + var old = scanTotalChars; |
| + scanTotalChars = 0; |
| + for (var source in files) { |
| + tokenize(source); |
| + } |
| + |
| + // Report size and scanning time again. See discussion above. |
|
Harry Terkelsen
2016/10/10 20:05:07
maybe "see discussion in parseFiles"?
Harry Terkelsen
2016/10/10 20:05:07
I don't see a comment discussing this?
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Acknowledged.
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
good point - I added this later and the comment wa
|
| + if (old != scanTotalChars) print('input size changed? ${old} chars'); |
| + report("scan", scanTimer.elapsedMicroseconds); |
| +} |
| + |
| + |
| +/// Parses every file in [files] and reports the time spent doing so. |
| +void parseFiles(Set<SourceFile> files) { |
| + // The code below will record again how many chars are scanned and how long it |
|
Harry Terkelsen
2016/10/10 20:05:07
move this comment to scanFiles?
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Done.
|
| + // 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 = new Stopwatch(); |
| + var old = scanTotalChars; |
| + scanTotalChars = 0; |
| + var parseTimer = new 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; |
|
Harry Terkelsen
2016/10/10 20:05:07
pTime -> parseTime?
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Done.
|
| + report("parse", pTime); |
| +} |
| + |
| +/// Add to [files] all sources reachable from [start]. |
| +Future collectSources(SourceFile start, Set<SourceFile> files) async { |
| + if (!files.add(start)) return; |
| + for (var directive in parseDirectives(start)) { |
| + var next = await loader.loadFile(start.uri.resolve(directive)); |
| + await collectSources(next, files); |
| + } |
| +} |
| + |
| +/// Uses the diet-parser to parse only directives in [source], returns the |
| +/// URIs seen in import/export/part directives in the file. |
| +Set<String> parseDirectives(SourceFile source) { |
| + var tokens = tokenize(source); |
| + var listener = new DirectiveListener(); |
| + new PartialParser(listener, const _ParserOptions()).parseUnit(tokens); |
| + return listener.targets; |
| +} |
| + |
| +/// Parse the full body of [source]. |
| +void parseFull(SourceFile source) { |
| + var tokens = tokenize(source); |
| + NodeListener listener = new NodeListener( |
| + const ScannerOptions(canUseNative: true), new FakeReporter(), null); |
| + Parser parser = new Parser(listener, const _ParserOptions()); |
| + parser.parseUnit(tokens); |
| + listener.popNode(); |
|
Harry Terkelsen
2016/10/10 20:05:07
why are you calling this?
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Ah - I meant to return the value (even though I do
|
| +} |
| + |
| +/// Scan [source] and return the first token produced by the scanner. |
| +Token tokenize(SourceFile source) { |
| + scanTimer.start(); |
| + scanTotalChars += source.length; |
| + var token = new Scanner(source).tokenize(); |
| + scanTimer.stop(); |
| + return token; |
| +} |
| + |
| +/// Report that metric [name] took [time] micro-seconds to process |
| +/// [scanTotalChars] characters. |
| +void report(String name, int time) { |
| + var sb = new StringBuffer(); |
| + sb.write('$name: $time us, ${time ~/ 1000} ms'); |
| + sb.write(', ${scanTotalChars * 1000 ~/ time} chars/ms'); |
| + print('$sb'); |
| +} |
| + |
| +/// Listener that parses out just the uri in imports, exports, and part |
| +/// directives. |
| +class DirectiveListener extends Listener { |
| + Set<String> targets = new Set<String>(); |
| + |
| + bool inDirective = false; |
| + void enterDirective() { |
| + inDirective = true; |
| + } |
| + |
| + void exitDirective() { |
| + inDirective = false; |
| + } |
| + |
| + void beginImport(Token importKeyword) => enterDirective(); |
| + void beginExport(Token token) => enterDirective(); |
| + void beginPart(Token token) => enterDirective(); |
| + |
| + void beginLiteralString(Token token) { |
| + if (inDirective) { |
| + var quotedString = token.value; |
| + targets.add(quotedString.substring(1, quotedString.length - 1)); |
| + } |
| + } |
| + |
| + void endExport(Token exportKeyword, Token semicolon) => exitDirective(); |
| + void endImport(Token importKeyword, Token deferredKeyword, Token asKeyword, |
| + Token semicolon) => |
| + exitDirective(); |
| + void endPart(Token partKeyword, Token semicolon) => exitDirective(); |
| +} |
| + |
| +Uri _libraryRoot = Platform.script.resolve('../../../sdk/'); |
| +Uri _platformConfigUri = _libraryRoot.resolve("lib/dart_server.platform"); |
| + |
| +class FakeReporter extends DiagnosticReporter { |
| + final hasReportedError = false; |
| + final options = new FakeReporterOptions(); |
| + |
| + withCurrentElement(e, f) => f(); |
| + log(m) => print(m); |
| + internalError(_, m) => print(m); |
| + spanFromSpannable(_) => null; |
| + |
| + void reportError(DiagnosticMessage message, |
| + [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) { |
| + print('error: ${message.message}'); |
| + } |
| + |
| + void reportWarning(DiagnosticMessage message, |
| + [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) { |
| + print('warning: ${message.message}'); |
| + } |
| + |
| + void reportHint(DiagnosticMessage message, |
| + [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) { |
| + print('hint: ${message.message}'); |
| + } |
| + |
| + void reportInfo(_, __, [Map arguments = const {}]) {} |
| + |
| + DiagnosticMessage createMessage(_, MessageKind kind, |
| + [Map arguments = const {}]) { |
| + MessageTemplate template = MessageTemplate.TEMPLATES[kind]; |
| + Message message = template.message(arguments, false); |
| + return new DiagnosticMessage(null, null, message); |
| + } |
| +} |
| + |
| +class FakeReporterOptions { |
| + bool get suppressHints => false; |
| + bool get hidePackageWarnings => false; |
| +} |
| + |
| +class _Loader { |
| + CompilerInput inputProvider; |
| + |
| + /// Maps dart-URIs to a known location in the sdk. |
| + Map<String, Uri> sdkLibraries; |
| + Map<Uri, SourceFile> _cache = {}; |
| + Map _debug = {}; |
|
Harry Terkelsen
2016/10/10 20:05:07
remove?
Siggi Cherem (dart-lang)
2016/10/10 21:46:12
Done.
|
| + Packages packages; |
| + |
| + _Loader(this.inputProvider, this.sdkLibraries, this.packages); |
| + |
| + Future<SourceFile> loadFile(Uri uri) async { |
| + if (!uri.isAbsolute) throw 'Relative uri $uri provided to readScript.'; |
| + Uri resourceUri = _translateUri(uri); |
| + if (resourceUri == null || resourceUri.scheme == 'dart-ext') { |
| + throw '$uri not resolved or unsupported.'; |
| + } |
| + var file = _cache[resourceUri]; |
|
Harry Terkelsen
2016/10/10 20:05:07
use putIfAbsent here
Siggi Cherem (dart-lang)
2016/10/10 21:46:13
Unfortunately I can't - the code that computes the
|
| + if (file != null) return _cache[resourceUri]; |
| + var r = _cache[resourceUri] = await _readFile(resourceUri); |
| + _debug[resourceUri] = uri; |
| + return r; |
| + } |
| + |
| + Future<SourceFile> _readFile(Uri uri) async { |
| + var data = await inputProvider.readFromUri(uri); |
| + if (data is List<int>) return new Utf8BytesSourceFile(uri, data); |
| + if (data is String) return new StringSourceFile.fromUri(uri, data); |
| + // TODO(sigmund): properly handle errors, just report, return null, wrap |
| + // above and continue... |
| + throw "Expected a 'String' or a 'List<int>' from the input " |
| + "provider, but got: ${data.runtimeType}."; |
| + } |
| + |
| + Uri _translateUri(Uri uri) { |
| + if (uri.scheme == 'dart') return sdkLibraries[uri.path]; |
| + if (uri.scheme == 'package') return _translatePackageUri(uri); |
| + return uri; |
| + } |
| + |
| + Uri _translatePackageUri(Uri uri) { |
| + checkValidPackageUri(uri); |
| + return packages.resolve(uri, notFound: (_) { |
| + print('$uri not found'); |
| + }); |
| + } |
| +} |
| + |
| +class _ParserOptions implements ParserOptions { |
| + const _ParserOptions(); |
| + bool get enableGenericMethodSyntax => true; |
| +} |