fbf331e0dd
Change-Id: I42b4a44fd6a2197e499e8623274b3cd1a4b5556f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394003 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Bob Nystrom <rnystrom@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
263 lines
8.0 KiB
Dart
263 lines
8.0 KiB
Dart
// Copyright (c) 2021, 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.
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import '../benchmarks.dart';
|
|
import 'memory_tests.dart';
|
|
|
|
class FlutterCompletionBenchmark extends Benchmark implements FlutterBenchmark {
|
|
static final das = FlutterCompletionBenchmark(
|
|
'das',
|
|
AnalysisServerBenchmarkTest.new,
|
|
);
|
|
|
|
static final lsp = FlutterCompletionBenchmark(
|
|
'lsp',
|
|
LspAnalysisServerBenchmarkTest.new,
|
|
);
|
|
|
|
final AbstractBenchmarkTest Function() testConstructor;
|
|
|
|
late final String flutterRepositoryPath;
|
|
|
|
FlutterCompletionBenchmark(String protocolName, this.testConstructor)
|
|
: super(
|
|
'$protocolName-flutter',
|
|
'Completion benchmarks with Flutter.',
|
|
kind: 'group',
|
|
);
|
|
|
|
@override
|
|
int get maxIterations => 1;
|
|
|
|
@override
|
|
Future<BenchMarkResult> run({
|
|
required String dartSdkPath,
|
|
bool quick = false,
|
|
bool verbose = false,
|
|
}) async {
|
|
if (!quick) {
|
|
deleteServerCache();
|
|
}
|
|
|
|
var test = testConstructor();
|
|
if (verbose) {
|
|
test.debugStdio();
|
|
}
|
|
|
|
var flutterPkgPath = path.join(
|
|
flutterRepositoryPath,
|
|
'packages',
|
|
'flutter',
|
|
);
|
|
|
|
// Open a small directory, but with the package config that allows us
|
|
// to analyze any file in `package:flutter`, including tests.
|
|
var startTimer = Stopwatch()..start();
|
|
await test.setUp(dartSdkPath, ['$flutterPkgPath/lib/src/physics']);
|
|
|
|
await test.analysisFinished;
|
|
startTimer.stop();
|
|
|
|
var result = CompoundBenchMarkResult(id);
|
|
result.add(
|
|
'start',
|
|
BenchMarkResult('micros', startTimer.elapsedMicroseconds),
|
|
);
|
|
|
|
// This is a scenario of an easy case - the file is small, less than
|
|
// 3KB, and we insert a prefix with a good selectivity. So, everything
|
|
// should be fast. We should just make sure to don't spend too much
|
|
// time analyzing, and do apply the filter.
|
|
// Total number of suggestions: 2322.
|
|
// Filtered to: 82.
|
|
// Long name: completion-smallFile-body
|
|
var name = 'completion-1';
|
|
result.add(
|
|
name,
|
|
BenchMarkResult(
|
|
'micros',
|
|
await _completionTiming(
|
|
test,
|
|
filePath: '$flutterPkgPath/lib/src/material/flutter_logo.dart',
|
|
uniquePrefix: 'Widget build(BuildContext context) {',
|
|
insertStringGenerator: () => 'M',
|
|
name: name,
|
|
),
|
|
),
|
|
);
|
|
|
|
if (!quick) {
|
|
// This scenario is relatively easy - the file is small, less than 3KB.
|
|
// But we don't have any prefix to filter, so if we don't restrict the
|
|
// number of suggestions, we might spend too much time serializing into
|
|
// JSON in the server, and deserializing on the client.
|
|
// Total number of suggestions: 2322.
|
|
// Filtered to: 2322.
|
|
// Long name: completion-smallFile-body-withoutPrefix
|
|
name = 'completion-2';
|
|
result.add(
|
|
name,
|
|
BenchMarkResult(
|
|
'micros',
|
|
await _completionTiming(
|
|
test,
|
|
filePath: '$flutterPkgPath/lib/src/material/flutter_logo.dart',
|
|
uniquePrefix: 'Widget build(BuildContext context) {',
|
|
insertStringGenerator: null,
|
|
name: name,
|
|
),
|
|
),
|
|
);
|
|
|
|
// The key part of this scenario is that we work in a relatively large
|
|
// file, about 340KB. So, it is expensive to parse and resolve. And
|
|
// we simulate changing it by typing a prefix, as users often do.
|
|
// The target method body is small, so something could be optimized.
|
|
// Total number of suggestions: 4654.
|
|
// Filtered to: 182.
|
|
// Long name: completion-smallLibraryCycle-largeFile-smallBody
|
|
name = 'completion-3';
|
|
result.add(
|
|
name,
|
|
BenchMarkResult(
|
|
'micros',
|
|
await _completionTiming(
|
|
test,
|
|
filePath: '$flutterPkgPath/test/material/text_field_test.dart',
|
|
uniquePrefix: 'getOpacity(WidgetTester tester, Finder finder) {',
|
|
insertStringGenerator: () => 'M',
|
|
name: name,
|
|
),
|
|
),
|
|
);
|
|
|
|
// In this scenario we change a file that is in a library cycle
|
|
// with 69 libraries. So, the implementation might discard information
|
|
// about all these libraries. We change a method body, so the API
|
|
// signature is the same, and we are able to reload these libraries
|
|
// from bytes. But this still costs something.
|
|
// Total number of suggestions: 3429.
|
|
// Filtered to: 133.
|
|
// Long name: completion-mediumLibraryCycle-mediumFile-smallBody
|
|
name = 'completion-4';
|
|
result.add(
|
|
name,
|
|
BenchMarkResult(
|
|
'micros',
|
|
await _completionTiming(
|
|
test,
|
|
filePath: '$flutterPkgPath/lib/src/material/app_bar.dart',
|
|
uniquePrefix: 'computeDryLayout(BoxConstraints constraints) {',
|
|
insertStringGenerator: () => 'M',
|
|
name: name,
|
|
),
|
|
),
|
|
);
|
|
|
|
// In this scenario is that we change a file that is in a library cycle
|
|
// with 69 libraries. Moreover, we change the API - the type of a
|
|
// formal parameter. So, potentially we need to relink the whole library
|
|
// cycle. This is expensive.
|
|
// Total number of suggestions: 1510.
|
|
// Filtered to: 0.
|
|
// Long name: completion-mediumLibraryCycle-mediumFile-api-parameterType
|
|
name = 'completion-5';
|
|
result.add(
|
|
name,
|
|
BenchMarkResult(
|
|
'micros',
|
|
await _completionTiming(
|
|
test,
|
|
filePath: '$flutterPkgPath/lib/src/material/app_bar.dart',
|
|
uniquePrefix: '_handleScrollNotification(ScrollNotification',
|
|
insertStringGenerator: _IncrementingStringGenerator().call,
|
|
name: name,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await test.shutdown();
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Perform completion in [filePath] at the end of the [uniquePrefix].
|
|
///
|
|
/// If [insertStringGenerator] is not `null`, insert it, and complete after
|
|
/// it. So, we can simulate user typing to start completion.
|
|
Future<int> _completionTiming(
|
|
AbstractBenchmarkTest test, {
|
|
required String filePath,
|
|
required String uniquePrefix,
|
|
required String Function()? insertStringGenerator,
|
|
String? name,
|
|
}) async {
|
|
var fileContent = File(filePath).readAsStringSync();
|
|
|
|
var prefixOffset = fileContent.indexOf(uniquePrefix);
|
|
if (prefixOffset == -1) {
|
|
throw StateError('Cannot find: $uniquePrefix');
|
|
}
|
|
if (fileContent.contains(uniquePrefix, prefixOffset + 1)) {
|
|
throw StateError('Not unique: $uniquePrefix');
|
|
}
|
|
|
|
var prefixEnd = prefixOffset + uniquePrefix.length;
|
|
|
|
await test.openFile(filePath, fileContent);
|
|
|
|
Future<void> perform({required bool isWarmUp}) async {
|
|
var completionOffset = prefixEnd;
|
|
|
|
if (insertStringGenerator != null) {
|
|
var insertString = insertStringGenerator();
|
|
completionOffset += insertString.length;
|
|
var newCode =
|
|
fileContent.substring(0, prefixEnd) +
|
|
insertString +
|
|
fileContent.substring(prefixEnd);
|
|
await test.updateFile(filePath, newCode);
|
|
}
|
|
|
|
await test.complete(filePath, completionOffset, isWarmUp: isWarmUp);
|
|
|
|
if (insertStringGenerator != null) {
|
|
await test.updateFile(filePath, fileContent);
|
|
}
|
|
}
|
|
|
|
// Perform warm-up.
|
|
// The cold start does not matter.
|
|
// The sustained performance is much more important.
|
|
const warmUpCount = 5;
|
|
for (var i = 0; i < warmUpCount; i++) {
|
|
await perform(isWarmUp: true);
|
|
}
|
|
|
|
const repeatCount = 5;
|
|
var timer = Stopwatch()..start();
|
|
for (var i = 0; i < repeatCount; i++) {
|
|
await perform(isWarmUp: false);
|
|
}
|
|
|
|
await test.closeFile(filePath);
|
|
|
|
return timer.elapsedMicroseconds ~/ repeatCount;
|
|
}
|
|
}
|
|
|
|
class _IncrementingStringGenerator {
|
|
int _value = 0;
|
|
|
|
String call() {
|
|
return '${_value++}';
|
|
}
|
|
}
|