[vm, lib] Refine List copy loops
`growableList.toList()` was 10-50% faster than `List.of(growableList)`.
This change erases the performance difference.
The loops were slightly different. The faster version created
the _List backing store for the list rather than assigning to the
_GrowableList.
It is not clear if the speedup is from an easier type test (the _List is
'raw'), or from a less indirect assignment, or from a fixed length list
having an an easier-to-optimize bounds check, or whether a non-zero loop
trip count enables any hoisting.
This change uses the faster pattern in the _GrowableList._ofXXXList
methods.
I tried to simplify `toList` to delegate to one of the methods
with the faster loop, but it was 2-5% slower on ListCopy.toList.2,
presumably the extra call is significant for a short list. If there
was an annotation for aggressive inlining into `toList` perhaps that
would close the gap.
dart-aot-null armv8c, 0.5 noise and over
ListCopy.List.num.from.2 24.17% (1.6 noise)
ListCopy.List.of.2 28.08% (1.7 noise)
ListCopy.spread.num.2 28.73% (2.1 noise)
ListCopy.List.num.from.100 54.49% (1.9 noise)
ListCopy.List.of.100 55.55% (1.9 noise)
ListCopy.spread.num.100 55.77% (2.0 noise)
Change-Id: I7b780835faf50b673fd4b1a08a8c4e2d350d7d5b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/224780
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/sdk/lib/_internal/vm/lib/array.dart b/sdk/lib/_internal/vm/lib/array.dart
index c897b67..ba48fbe 100644
--- a/sdk/lib/_internal/vm/lib/array.dart
+++ b/sdk/lib/_internal/vm/lib/array.dart
@@ -61,8 +61,11 @@
factory _List._ofGrowableList(_GrowableList<E> elements) {
final int length = elements.length;
final list = _List<E>(length);
- for (int i = 0; i < length; i++) {
- list[i] = elements[i];
+ // TODO(30102): Remove this loop zero-trip guard.
+ if (length > 0) {
+ for (int i = 0; i < length; i++) {
+ list[i] = elements[i];
+ }
}
return list;
}
@@ -70,8 +73,11 @@
factory _List._ofList(_List<E> elements) {
final int length = elements.length;
final list = _List<E>(length);
- for (int i = 0; i < length; i++) {
- list[i] = elements[i];
+ // TODO(30102): Remove this loop zero-trip guard.
+ if (length > 0) {
+ for (int i = 0; i < length; i++) {
+ list[i] = elements[i];
+ }
}
return list;
}
@@ -79,8 +85,11 @@
factory _List._ofImmutableList(_ImmutableList<E> elements) {
final int length = elements.length;
final list = _List<E>(length);
- for (int i = 0; i < length; i++) {
- list[i] = elements[i];
+ // TODO(30102): Remove this loop zero-trip guard.
+ if (length > 0) {
+ for (int i = 0; i < length; i++) {
+ list[i] = elements[i];
+ }
}
return list;
}
diff --git a/sdk/lib/_internal/vm/lib/growable_array.dart b/sdk/lib/_internal/vm/lib/growable_array.dart
index 516996f..c6d9e46 100644
--- a/sdk/lib/_internal/vm/lib/growable_array.dart
+++ b/sdk/lib/_internal/vm/lib/growable_array.dart
@@ -110,7 +110,10 @@
// Specialization of List.empty constructor for growable == true.
// Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
@pragma("vm:prefer-inline")
- factory _GrowableList.empty() => _GrowableList(0);
+ factory _GrowableList.empty() {
+ // Specialization of `return _GrowableList(0);`.
+ return _GrowableList<T>._withData(_emptyList);
+ }
// Specialization of List.filled constructor for growable == true.
// Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
@@ -154,43 +157,61 @@
factory _GrowableList._ofList(_List<T> elements) {
final int length = elements.length;
- final list = _GrowableList<T>(length);
- for (int i = 0; i < length; i++) {
- list[i] = elements[i];
+ if (length > 0) {
+ final data = _List(_adjustedCapacity(length));
+ for (int i = 0; i < length; i++) {
+ data[i] = elements[i];
+ }
+ final list = _GrowableList<T>._withData(data);
+ list._setLength(length);
+ return list;
}
- return list;
+ return _GrowableList<T>.empty();
}
factory _GrowableList._ofGrowableList(_GrowableList<T> elements) {
final int length = elements.length;
- final list = _GrowableList<T>(length);
- for (int i = 0; i < length; i++) {
- list[i] = elements[i];
+ if (length > 0) {
+ final data = _List(_adjustedCapacity(length));
+ for (int i = 0; i < length; i++) {
+ data[i] = elements[i];
+ }
+ final list = _GrowableList<T>._withData(data);
+ list._setLength(length);
+ return list;
}
- return list;
+ return _GrowableList<T>.empty();
}
factory _GrowableList._ofImmutableList(_ImmutableList<T> elements) {
final int length = elements.length;
- final list = _GrowableList<T>(length);
- for (int i = 0; i < length; i++) {
- list[i] = elements[i];
+ if (length > 0) {
+ final data = _List(_adjustedCapacity(length));
+ for (int i = 0; i < length; i++) {
+ data[i] = elements[i];
+ }
+ final list = _GrowableList<T>._withData(data);
+ list._setLength(length);
+ return list;
}
- return list;
+ return _GrowableList<T>.empty();
}
factory _GrowableList._ofEfficientLengthIterable(
EfficientLengthIterable<T> elements) {
final int length = elements.length;
- final list = _GrowableList<T>(length);
if (length > 0) {
+ final data = _List(_adjustedCapacity(length));
int i = 0;
for (var element in elements) {
- list[i++] = element;
+ data[i++] = element;
}
if (i != length) throw ConcurrentModificationError(elements);
+ final list = _GrowableList<T>._withData(data);
+ list._setLength(length);
+ return list;
}
- return list;
+ return _GrowableList<T>.empty();
}
factory _GrowableList._ofOther(Iterable<T> elements) {
@@ -358,11 +379,13 @@
// Use shared empty list as backing.
return _emptyList;
}
- // Round up size to the next odd number, since this is free
- // because of alignment requirements of the GC.
- return new _List(capacity | 1);
+ return _List(_adjustedCapacity(capacity));
}
+ // Round up size to the next odd number, since this is free
+ // because of alignment requirements of the GC.
+ static int _adjustedCapacity(int capacity) => capacity | 1;
+
// Grow from 0 to 3, and then double + 1.
int _nextCapacity(int old_capacity) => (old_capacity * 2) | 3;
@@ -501,14 +524,22 @@
}
List<T> toList({bool growable: true}) {
+ // TODO(sra): We should be able to replace the following with:
+ //
+ // return growable
+ // ? _GrowableList<T>._ofGrowableList(this)
+ // : _List<T>._ofGrowableList(this);
+ //
+ // However, the extra call causes a 5% regression in `ListCopy.toList.2`.
+
final length = this.length;
if (growable) {
if (length > 0) {
- final list = new _List(length);
+ final data = new _List(_adjustedCapacity(length));
for (int i = 0; i < length; i++) {
- list[i] = this[i];
+ data[i] = this[i];
}
- final result = new _GrowableList<T>._withData(list);
+ final result = new _GrowableList<T>._withData(data);
result._setLength(length);
return result;
}