Skip to content

Commit 8e4a7ab

Browse files
perf(core): avoid repeated access to LContainer and trackBy calculation (#52227)
Assuming that the trackBy function is a pure derivation from the collection object and its index, we can skip trackBy calculation if items in the live and new colelction have the same identity and index. Additionally this change minimizes access to the LContainer array. PR Close #52227
1 parent 3fec271 commit 8e4a7ab

File tree

1 file changed

+40
-14
lines changed

1 file changed

+40
-14
lines changed

packages/core/src/render3/list_reconciliation.ts

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ export abstract class LiveCollection<T, V> {
4646
}
4747
}
4848

49+
function valuesMatching<V>(
50+
liveIdx: number, liveValue: V, newIdx: number, newValue: V,
51+
trackBy: TrackByFunction<V>): number {
52+
if (liveIdx === newIdx && Object.is(liveValue, newValue)) {
53+
// matching and no value identity to update
54+
return 1;
55+
} else if (Object.is(trackBy(liveIdx, liveValue), trackBy(newIdx, newValue))) {
56+
// matching but requires value identity update
57+
return -1;
58+
}
59+
60+
return 0;
61+
}
62+
4963
/**
5064
* The live collection reconciliation algorithm that perform various in-place operations, so it
5165
* reflects the content of the new (incoming) collection.
@@ -84,34 +98,42 @@ export function reconcile<T, V>(
8498
while (liveStartIdx <= liveEndIdx && liveStartIdx <= newEndIdx) {
8599
// compare from the beginning
86100
const liveStartValue = liveCollection.at(liveStartIdx);
87-
const liveStartKey = trackByFn(liveStartIdx, liveStartValue);
88101
const newStartValue = newCollection[liveStartIdx];
89-
const newStartKey = trackByFn(liveStartIdx, newStartValue);
90-
if (Object.is(liveStartKey, newStartKey)) {
91-
liveCollection.updateValue(liveStartIdx, newStartValue);
102+
const isStartMatching =
103+
valuesMatching(liveStartIdx, liveStartValue, liveStartIdx, newStartValue, trackByFn);
104+
if (isStartMatching !== 0) {
105+
if (isStartMatching < 0) {
106+
liveCollection.updateValue(liveStartIdx, newStartValue);
107+
}
92108
liveStartIdx++;
93109
continue;
94110
}
95111

96112
// compare from the end
97113
// TODO(perf): do _all_ the matching from the end
98114
const liveEndValue = liveCollection.at(liveEndIdx);
99-
const liveEndKey = trackByFn(liveEndIdx, liveEndValue);
100-
const newEndItem = newCollection[newEndIdx];
101-
const newEndKey = trackByFn(newEndIdx, newEndItem);
102-
if (Object.is(liveEndKey, newEndKey)) {
103-
liveCollection.updateValue(liveEndIdx, newEndItem);
115+
const newEndValue = newCollection[newEndIdx];
116+
const isEndMatching =
117+
valuesMatching(liveEndIdx, liveEndValue, newEndIdx, newEndValue, trackByFn);
118+
if (isEndMatching !== 0) {
119+
if (isEndMatching < 0) {
120+
liveCollection.updateValue(liveEndIdx, newEndValue);
121+
}
104122
liveEndIdx--;
105123
newEndIdx--;
106124
continue;
107125
}
108126

109127
// Detect swap / moves:
128+
const liveStartKey = trackByFn(liveStartIdx, liveStartValue);
129+
const liveEndKey = trackByFn(liveEndIdx, liveEndValue);
130+
const newStartKey = trackByFn(liveStartIdx, newStartValue);
131+
const newEndKey = trackByFn(newEndIdx, newEndValue);
110132
if (Object.is(newStartKey, liveEndKey) && Object.is(newEndKey, liveStartKey)) {
111133
// swap on both ends;
112134
liveCollection.swap(liveStartIdx, liveEndIdx);
113135
liveCollection.updateValue(liveStartIdx, newStartValue);
114-
liveCollection.updateValue(liveEndIdx, newEndItem);
136+
liveCollection.updateValue(liveEndIdx, newEndValue);
115137
newEndIdx--;
116138
liveStartIdx++;
117139
liveEndIdx--;
@@ -164,13 +186,17 @@ export function reconcile<T, V>(
164186
const newCollectionIterator = newCollection[Symbol.iterator]();
165187
let newIterationResult = newCollectionIterator.next();
166188
while (!newIterationResult.done && liveStartIdx <= liveEndIdx) {
189+
const liveValue = liveCollection.at(liveStartIdx);
167190
const newValue = newIterationResult.value;
168191
const newKey = trackByFn(liveStartIdx, newValue);
169-
const liveValue = liveCollection.at(liveStartIdx);
170192
const liveKey = trackByFn(liveStartIdx, liveValue);
171-
if (Object.is(liveKey, newKey)) {
172-
// found a match - move on
173-
liveCollection.updateValue(liveStartIdx, newValue);
193+
const isStartMatching =
194+
valuesMatching(liveStartIdx, liveValue, liveStartIdx, newValue, trackByFn);
195+
if (isStartMatching !== 0) {
196+
// found a match - move on, but update value
197+
if (isStartMatching < 0) {
198+
liveCollection.updateValue(liveStartIdx, newValue);
199+
}
174200
liveStartIdx++;
175201
newIterationResult = newCollectionIterator.next();
176202
} else {

0 commit comments

Comments
 (0)