[analyzer] Use QueueList by default in SourceFileEdit

Adding edits (e.g. via `dart fix --apply` are often done with
`List.insert(0, whatnot)` which takes O(n) time.
Here QueueList that can insert fast at both ends is used instead
(and we use `addFirst` instead of `insert(0)`.

On the example from
https://github.com/feinstein/google-i18n-address-dart.git we go from:

```
$ time dart fix --use-aot-snapshot --apply
[...]
249517 fixes made in 255 files.

real    3m55.810s
user    4m1.209s
sys     0m3.714s

(resetting)
$ time dart fix --use-aot-snapshot --apply
[...]
249517 fixes made in 255 files.

real    3m33.966s
user    3m37.588s
sys     0m2.058s

(resetting)
$ time dart fix --use-aot-snapshot --apply
[...]
249517 fixes made in 255 files.

real    3m36.525s
user    3m40.083s
sys     0m1.907s
```

to:

```
$ time dart fix --use-aot-snapshot --apply
[...]
249517 fixes made in 255 files.

real    0m9.970s
user    0m12.676s
sys     0m2.100s

(resetting)
$ time dart fix --use-aot-snapshot --apply
[...]
249517 fixes made in 255 files.

real    0m9.862s
user    0m12.926s
sys     0m1.797s

(resetting)
$ time dart fix --use-aot-snapshot --apply
[...]
249517 fixes made in 255 files.

real    0m9.612s
user    0m12.712s
sys     0m1.834s
```

Statistics on the `real` runtime:

```
    N           Min           Max        Median           Avg        Stddev
x   3       213.966        235.81       216.525     222.10033     11.941664
+   3         9.612          9.97         9.862     9.8146667    0.18363369
Difference at 95.0% confidence
        -212.286 +/- 19.1415
        -95.581% +/- 8.61838%
        (Student's t, pooled s = 8.44503)
```

For `lsp_many_prefer_single_quotes_violations_benchmark.dart --sizes=3200`:

Before from something like:

```
Initial analysis: 0.115654
First code action call: 0.835152
Subsequent action call 1: 0.538592
Subsequent action call 2: 0.561636
Select all code action call: 1.564402
```

After to something like:
```
Initial analysis: 0.086985
First code action call: 0.411660
Subsequent action call 1: 0.171566
Subsequent action call 2: 0.193708
Select all code action call: 1.107339
```

Statistics on 5 runs gives:

First code action call:

```
Difference at 95.0% confidence
        -0.44381 +/- 0.0261597
        -52.4602% +/- 3.09218%
        (Student's t, pooled s = 0.0179367)
```

Subsequent action call 1:

```
Difference at 95.0% confidence
        -0.381012 +/- 0.0195618
        -69.9139% +/- 3.5895%
        (Student's t, pooled s = 0.0134128)
```

Subsequent action call 2:

```
Difference at 95.0% confidence
        -0.360077 +/- 0.0265277
        -64.634% +/- 4.76173%
        (Student's t, pooled s = 0.0181891)
```

Select all code action call:

```
Difference at 95.0% confidence
        -0.405855 +/- 0.027662
        -26.7277% +/- 1.82169%
        (Student's t, pooled s = 0.0189668)
```

Change-Id: I3868afaa8c32a24c01c3a52bd8a53d5e8e4e3afe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427401
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Jens Johansen
2025-05-16 00:45:34 -07:00
committed by Commit Queue
parent 1f26ddfee2
commit 0d4b08caaa
7 changed files with 33 additions and 5 deletions
@@ -8,6 +8,8 @@
import 'dart:convert' hide JsonDecoder;
import 'package:collection/collection.dart' show QueueList;
import 'package:analysis_server_client/src/protocol/protocol_internal.dart';
// ignore_for_file: flutter_style_todos
@@ -3678,7 +3680,7 @@ class SourceFileEdit implements HasToJson {
List<SourceEdit> edits;
SourceFileEdit(this.file, this.fileStamp, {List<SourceEdit>? edits})
: edits = edits ?? <SourceEdit>[];
: edits = edits ?? QueueList<SourceEdit>();
factory SourceFileEdit.fromJson(
JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -45,7 +45,12 @@ void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit,
index++;
}
}
edits.insert(index, sourceEdit);
if (index == 0 && edits is Queue) {
var q = edits as Queue;
q.addFirst(sourceEdit);
} else {
edits.insert(index, sourceEdit);
}
}
/// Adds [edit] to the [FileEdit] for the given [file].
+1
View File
@@ -16,6 +16,7 @@ resolution: workspace
# Use 'any' constraints here; we get our versions from the DEPS file.
dependencies:
collection: any
path: any
pub_semver: any
@@ -8,6 +8,8 @@
import 'dart:convert' hide JsonDecoder;
import 'package:collection/collection.dart' show QueueList;
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';
import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
@@ -3759,7 +3761,7 @@ class SourceFileEdit implements HasToJson {
List<SourceEdit> edits;
SourceFileEdit(this.file, this.fileStamp, {List<SourceEdit>? edits})
: edits = edits ?? <SourceEdit>[];
: edits = edits ?? QueueList<SourceEdit>();
factory SourceFileEdit.fromJson(
JsonDecoder jsonDecoder, String jsonPath, Object? json,
@@ -78,7 +78,12 @@ void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit,
newEdit: sourceEdit, existingEdit: nextEdit);
}
}
edits.insert(index, sourceEdit);
if (index == 0 && edits is Queue) {
var q = edits as Queue;
q.addFirst(sourceEdit);
} else {
edits.insert(index, sourceEdit);
}
}
/// Adds [edit] to the [FileEdit] for the given [file].
@@ -54,6 +54,12 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
'TypeHierarchyItem': ['interfaces', 'mixins', 'subclasses'],
};
/// Class members for which the list type should not be the default,
/// but QueueList for performance reasons.
static const Map<String, List<String>> _useQueueList = {
'SourceFileEdit': ['edits'],
};
/// The disclaimer added to the documentation comment for each of the classes
/// that are generated.
static const String disclaimer =
@@ -461,7 +467,12 @@ class CodegenProtocolVisitor extends DartCodegenVisitor with CodeGenerator {
// given, the constructor should populate with the empty list.
var fieldType = field.type;
if (fieldType is TypeList) {
var defaultValue = '<${dartType(fieldType.itemType)}>[]';
String defaultValue;
if (_useQueueList[className]?.contains(field.name) ?? false) {
defaultValue = 'QueueList<${dartType(fieldType.itemType)}>()';
} else {
defaultValue = '<${dartType(fieldType.itemType)}>[]';
}
initializers.add('${field.name} = ${field.name} ?? $defaultValue');
} else {
throw Exception("Don't know how to create default field value.");
@@ -45,6 +45,8 @@ class CodegenCommonVisitor extends CodegenProtocolVisitor {
void emitImports() {
writeln("import 'dart:convert' hide JsonDecoder;");
writeln();
writeln("import 'package:collection/collection.dart' show QueueList;");
writeln();
if (forClient) {
writeln(
"import 'package:analysis_server_client/src/protocol/protocol_internal.dart';");