Skip to content

Commit 90fb141

Browse files
jensjohaCommit Queue
authored andcommitted
[analyzer] Speedup assist calls when file has non-Windows line-endings
Before an assist call on a file with non-windows lineendings would first search through the entire file for Windows line endings, then search for the non-Windows line endings. As such Windows line endings was faster. This CL changes the semantics slightly from "Windows line endings if any Windows line endings exist" to "whatever line endings are first used" which should be good. It speeds up assist calls with non-Windows line endings: Statistics on 5 runs each: ``` Windows / 2000: No change. Windows / 4000: No change. Windows / 8000: No change. Unix / 2000: 1000 assist calls: -46.9900% +/- 6.1715% (-0.88 +/- 0.12) (1.86 -> 0.99) Unix / 4000: 1000 assist calls: -59.1561% +/- 6.3982% (-1.79 +/- 0.19) (3.03 -> 1.24) Unix / 8000: 1000 assist calls: -63.8429% +/- 4.3083% (-3.68 +/- 0.25)(5.76 -> 2.08) ``` Change-Id: I61278ed4ad31b891e87951da0a6162fe37376888 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437224 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Jens Johansen <[email protected]>
1 parent 28d25e4 commit 90fb141

File tree

3 files changed

+170
-8
lines changed

3 files changed

+170
-8
lines changed

pkg/analysis_server/test/src/services/correction/assist/flutter_wrap_generic_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ class FakeFlutter {
157157

158158
Future<void> test_multiLines_eol2() async {
159159
await resolveTestCode('''
160-
import 'package:flutter/widgets.dart';
160+
import 'package:flutter/widgets.dart';\r
161161
class FakeFlutter {\r
162162
Widget f() {\r
163163
return Container(\r
@@ -174,7 +174,7 @@ class FakeFlutter {\r
174174
}\r
175175
''');
176176
await assertHasAssist('''
177-
import 'package:flutter/widgets.dart';
177+
import 'package:flutter/widgets.dart';\r
178178
class FakeFlutter {\r
179179
Widget f() {\r
180180
return Container(\r
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import '../language_server_benchmark.dart';
8+
import '../lsp_messages.dart';
9+
import '../run_utils.dart';
10+
11+
/// In a big file ask for assists many times (simulating the user using the
12+
/// arrow keys to go back and forth on a line) and record the total time to do
13+
/// it.
14+
Future<void> main(List<String> args) async {
15+
await runHelper(
16+
args,
17+
LspManyAssistCalls.new,
18+
createData,
19+
sizeOptions: [2000, 4000, 8000],
20+
extraIterations: getExtraIterations,
21+
runAsLsp: true,
22+
);
23+
}
24+
25+
RunDetails createData(
26+
Uri packageDirUri,
27+
Uri outerDirForAdditionalData,
28+
int size,
29+
LineEndings? lineEnding,
30+
List<String> args, {
31+
// unused
32+
required dynamic extraInformation,
33+
}) {
34+
Uri libDirUri = packageDirUri.resolve('lib/');
35+
Directory.fromUri(libDirUri).createSync();
36+
37+
var uri = libDirUri.resolve('main.dart');
38+
var content = <String>[];
39+
for (int i = 0; i < size; i++) {
40+
var className = 'Class$i';
41+
content.add('class $className {');
42+
content.add(' final int foo;');
43+
content.add(' $className(this.foo) {');
44+
content.add(' print("Hello from class $className");');
45+
content.add(' print("$className.foo = \$foo");');
46+
content.add(' }');
47+
content.add('}');
48+
}
49+
var lineEndingString = lineEnding == LineEndings.Windows ? '\r\n' : '\n';
50+
var contentString = content.join(lineEndingString);
51+
File.fromUri(uri).writeAsStringSync(contentString);
52+
return RunDetails(
53+
mainFile: FileContentPair(uri, contentString),
54+
lineEnding: lineEndingString,
55+
);
56+
}
57+
58+
List<LineEndings> getExtraIterations(List<String> args) {
59+
var lineEndings = LineEndings.values;
60+
for (String arg in args) {
61+
if (arg.startsWith('--types=')) {
62+
lineEndings = [];
63+
for (var type in arg.substring('--types='.length).split(',')) {
64+
type = type.toLowerCase();
65+
for (var value in LineEndings.values) {
66+
if (value.name.toLowerCase().contains(type)) {
67+
lineEndings.add(value);
68+
}
69+
}
70+
}
71+
}
72+
}
73+
return lineEndings;
74+
}
75+
76+
class FileContentPair {
77+
final Uri uri;
78+
final String content;
79+
80+
FileContentPair(this.uri, this.content);
81+
}
82+
83+
enum LineEndings { Windows, Unix }
84+
85+
class LspManyAssistCalls extends DartLanguageServerBenchmark {
86+
@override
87+
final Uri rootUri;
88+
@override
89+
final Uri cacheFolder;
90+
91+
final RunDetails runDetails;
92+
93+
LspManyAssistCalls(
94+
super.args,
95+
this.rootUri,
96+
this.cacheFolder,
97+
this.runDetails,
98+
) : super(useLspProtocol: true);
99+
100+
@override
101+
LaunchFrom get launchFrom => LaunchFrom.Dart;
102+
103+
@override
104+
Future<void> afterInitialization() async {
105+
var lines = runDetails.mainFile.content.split(runDetails.lineEnding);
106+
await send(
107+
LspMessages.open(runDetails.mainFile.uri, 1, runDetails.mainFile.content),
108+
);
109+
110+
Future<Duration> timeAssist(int lineNumber, int column) async {
111+
var codeActionStopwatch = Stopwatch()..start();
112+
var codeActionFuture = (await send(
113+
LspMessages.codeAction(
114+
largestIdSeen + 1,
115+
runDetails.mainFile.uri,
116+
line: lineNumber,
117+
character: column,
118+
),
119+
))!.completer.future.then((result) {
120+
codeActionStopwatch.stop();
121+
return result;
122+
});
123+
await codeActionFuture;
124+
return codeActionStopwatch.elapsed;
125+
}
126+
127+
const lineNumber = 4;
128+
String line = lines[lineNumber];
129+
if (line != r' print("Class0.foo = $foo");') {
130+
throw 'Unexpected change in benchmark.';
131+
}
132+
133+
// Warmup.
134+
for (int i = 0; i < 100; i++) {
135+
await timeAssist(lineNumber, 0);
136+
}
137+
138+
// Time 1,000 assist calls.
139+
int calls = 0;
140+
const int numCalls = 1000;
141+
Duration sum = Duration.zero;
142+
while (true) {
143+
for (int column = 0; column < line.length; column++) {
144+
var duration = await timeAssist(lineNumber, column);
145+
sum += duration;
146+
if (calls++ >= numCalls) break;
147+
}
148+
if (calls++ >= numCalls) break;
149+
}
150+
151+
durationInfo.add(DurationInfo('$numCalls assist calls', sum));
152+
}
153+
}
154+
155+
class RunDetails {
156+
final String lineEnding;
157+
final FileContentPair mainFile;
158+
159+
RunDetails({required this.mainFile, required this.lineEnding});
160+
}

pkg/analysis_server_plugin/lib/edit/correction_utils.dart

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,17 @@ final class CorrectionUtils {
4040
return endOfLine;
4141
}
4242

43-
if (_buffer.contains('\r\n')) {
44-
return _endOfLine = '\r\n';
43+
var indexOfNewline = _buffer.indexOf('\n');
44+
if (indexOfNewline < 0) {
45+
// No `\n` (and thus no `\r\n` either) found.
46+
return Platform.lineTerminator;
4547
}
4648

47-
if (_buffer.contains('\n')) {
48-
return _endOfLine = '\n';
49+
if (indexOfNewline > 0 &&
50+
_buffer.codeUnitAt(indexOfNewline - 1) == 13 /* \r */) {
51+
return _endOfLine = '\r\n';
4952
}
50-
51-
return Platform.lineTerminator;
53+
return _endOfLine = '\n';
5254
}
5355

5456
String get oneIndent => _oneIndent;

0 commit comments

Comments
 (0)