Skip to content

Commit 082ea29

Browse files
committed
perf(runtime-vapor): fold the v-for old-index record into the LIS input
1 parent d5446da commit 082ea29

1 file changed

Lines changed: 32 additions & 28 deletions

File tree

packages/runtime-vapor/src/apiCreateFor.ts

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,8 @@ export const createFor = (
270270
const e2 = oldLength - endOffset
271271
const e3 = newLength - endOffset
272272

273-
// parallel arrays holding blocks that need a mount or a move, in
274-
// ascending index order (built packed, so a small change in a large
275-
// list only allocates for the actual churn). After the reuse pass
276-
// `queuedOldIndex[q]` distinguishes them: the block's old position
277-
// for a reuse (its ForBlock is already in `newBlocks`), or -1 for a
278-
// fresh mount.
273+
// new indices needing a mount or a move, ascending (built packed, so a
274+
// small change in a large list only allocates for the actual churn)
279275
const queuedIndices: number[] = []
280276
const oldKeyIndexMap = new Map<any, number>()
281277

@@ -303,14 +299,19 @@ export const createFor = (
303299
}
304300

305301
let queuedLength = queuedIndices.length
306-
const queuedOldIndex: number[] = new Array(queuedLength)
302+
// `getSequence` input, doubling as the old-position record: the
303+
// block's old index + 1 (0 stays free as the "skip" marker, and also
304+
// marks a fresh mount). The bounds filter below zeroes reuses that may
305+
// not stay put; the apply pass tells move from mount by
306+
// `newBlocks[index]`, which the reuse pass has already filled in.
307+
const sources: number[] = new Array(queuedLength)
307308
let mountCounter = 0
308309

309310
if (oldKeyIndexMap.size === 0) {
310311
// pure append/replace: nothing to pair up. Plain loop over fill():
311312
// the inlined monomorphic store beats the generic builtin on a
312313
// freshly allocated (holey) array.
313-
for (let q = 0; q < queuedLength; q++) queuedOldIndex[q] = -1
314+
for (let q = 0; q < queuedLength; q++) sources[q] = 0
314315
mountCounter = queuedLength
315316
} else {
316317
for (let q = queuedLength - 1; q >= 0; q--) {
@@ -321,9 +322,9 @@ export const createFor = (
321322
oldKeyIndexMap.delete(key)
322323
const reusedBlock = (newBlocks[index] = oldBlocks[oldIndex])
323324
updateAt(reusedBlock, source, index)
324-
queuedOldIndex[q] = oldIndex
325+
sources[q] = oldIndex + 1
325326
} else {
326-
queuedOldIndex[q] = -1
327+
sources[q] = 0
327328
mountCounter++
328329
}
329330
}
@@ -364,7 +365,7 @@ export const createFor = (
364365
// neighbours. Not minimal in general, but it never walks the
365366
// untouched majority.
366367
let sequence: number[] | undefined
367-
let sequenceInput: number[] | undefined
368+
let allKept = false
368369
if (mountCounter !== queuedLength) {
369370
const span = queuedIndices[queuedLength - 1] - queuedIndices[0] + 1
370371
if (stationaryIndices && queuedLength * 2 >= span) {
@@ -377,28 +378,28 @@ export const createFor = (
377378
if (index > firstIndex && index < lastIndex) {
378379
queuedIndices.push(index)
379380
// a prefix stationary sits at the same index in both lists
380-
queuedOldIndex.push(index)
381+
sources.push(index + 1)
381382
}
382383
}
383384
if (queuedIndices.length !== queuedLength) {
384-
sortQueueByIndex(queuedIndices, queuedOldIndex)
385+
sortQueueByIndex(queuedIndices, sources)
385386
queuedLength = queuedIndices.length
386387
}
387388
}
388389

389-
// `getSequence` input: the old position (+1, so 0 stays free as its
390-
// "skip" marker) of every block allowed to stay put, 0 for the rest.
391-
sequenceInput = new Array(queuedLength)
392390
let eligible = 0
391+
// if the eligible old positions already ascend, they are all in
392+
// relative order and every one of them stays put — no LIS needed
393+
let moved = false
394+
let maxSource = 0
393395
if (queuedLength * 2 >= span) {
394396
// dense: no bounds, every reuse competes for the LIS
395397
for (let q = 0; q < queuedLength; q++) {
396-
const oldIndex = queuedOldIndex[q]
397-
if (oldIndex < 0) {
398-
sequenceInput[q] = 0
399-
} else {
400-
sequenceInput[q] = oldIndex + 1
398+
const value = sources[q]
399+
if (value !== 0) {
401400
eligible++
401+
if (value < maxSource) moved = true
402+
else maxSource = value
402403
}
403404
}
404405
} else {
@@ -423,23 +424,26 @@ export const createFor = (
423424
const nextIndex = queuedIndices[segEnd] + 1
424425
const upperBound = nextIndex < e3 ? nextIndex : e2
425426
for (let q = seg; q <= segEnd; q++) {
426-
const oldIndex = queuedOldIndex[q]
427+
const value = sources[q]
428+
const oldIndex = value - 1
427429
if (
428430
oldIndex < 0 ||
429431
oldIndex <= lowerBound ||
430432
oldIndex >= upperBound
431433
) {
432-
sequenceInput[q] = 0
434+
sources[q] = 0
433435
} else {
434-
sequenceInput[q] = oldIndex + 1
435436
eligible++
437+
if (value < maxSource) moved = true
438+
else maxSource = value
436439
}
437440
}
438441
seg = segEnd + 1
439442
}
440443
}
441444

442-
if (eligible) sequence = getSequence(sequenceInput)
445+
if (eligible && moved) sequence = getSequence(sources)
446+
else if (eligible) allKept = true
443447
}
444448

445449
// apply back-to-front so every block can anchor on the finalized
@@ -453,12 +457,12 @@ export const createFor = (
453457
let cachedAnchor: Node | undefined
454458
for (let q = queuedLength - 1; q >= 0; q--) {
455459
const index = queuedIndices[q]
456-
let isKept = false
460+
let isKept = allKept && sources[q] !== 0
457461
if (sequenceEnd >= 0 && sequence![sequenceEnd] === q) {
458462
sequenceEnd--
459463
// `getSequence` seeds its result with index 0, so it can report
460464
// a leading entry that was never selected; skip that marker
461-
isKept = sequenceInput![q] !== 0
465+
isKept = sources[q] !== 0
462466
}
463467

464468
// A block that renders nothing has no first node, so look past it
@@ -501,7 +505,7 @@ export const createFor = (
501505
cachedAnchor = anchorNode
502506

503507
if (isKept) continue
504-
if (queuedOldIndex[q] >= 0) {
508+
if (newBlocks[index] !== undefined) {
505509
insertForBlock(newBlocks[index], anchorNode)
506510
} else {
507511
mount(source, index, anchorNode)

0 commit comments

Comments
 (0)