Skip to content

Commit 9e38eb3

Browse files
committed
Add some assertions for perf expectations
1 parent 428bc2f commit 9e38eb3

File tree

2 files changed

+123
-2
lines changed

2 files changed

+123
-2
lines changed

packages/toolkit/src/entities/sorted_state_adapter.ts

+97
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,103 @@ export function createSortedStateAdapter<T, Id extends EntityId>(
499499
}
500500
}
501501

502+
const mergeJackman: MergeFunction = (
503+
state,
504+
addedItems,
505+
updatedIds,
506+
replacedIds,
507+
) => {
508+
const entities = state.entities as Record<Id, T>
509+
let ids = state.ids as Id[]
510+
if (replacedIds) {
511+
ids = Array.from(new Set(ids))
512+
}
513+
const existingSortedItems = ids // Array.from(new Set(state.ids as Id[]))
514+
.map((id) => entities[id])
515+
.filter(Boolean)
516+
517+
function findInsertIndex2<T>(
518+
sortedItems: T[],
519+
item: T,
520+
comparisonFunction: Comparer<T>,
521+
lowIndexOverride?: number,
522+
): number {
523+
let lowIndex = lowIndexOverride ?? 0
524+
let highIndex = sortedItems.length
525+
while (lowIndex < highIndex) {
526+
const middleIndex = (lowIndex + highIndex) >>> 1
527+
const currentItem = sortedItems[middleIndex]
528+
if (comparisonFunction(item, currentItem) > 0) {
529+
lowIndex = middleIndex + 1
530+
} else {
531+
highIndex = middleIndex
532+
}
533+
}
534+
535+
return lowIndex
536+
}
537+
538+
if (addedItems.length) {
539+
const newEntities = addedItems.slice().sort(comparer)
540+
541+
// Insert/overwrite all new/updated
542+
newEntities.forEach((model) => {
543+
entities[selectId(model)] = model
544+
})
545+
546+
const firstInstanceId = newEntities[0]
547+
const lastInstanceId = newEntities[newEntities.length - 1]
548+
549+
const startIndex = findInsertIndex2(
550+
existingSortedItems,
551+
firstInstanceId,
552+
comparer,
553+
)
554+
const endIndex = findInsertIndex2(
555+
existingSortedItems,
556+
lastInstanceId,
557+
comparer,
558+
startIndex,
559+
)
560+
561+
const overlappingExistingIds = existingSortedItems.slice(
562+
startIndex,
563+
endIndex,
564+
)
565+
let newIdIndexOfLastInsert = 0
566+
let lastRelativeInsertIndex = 0
567+
for (let i = 1; i < newEntities.length; i++) {
568+
const relativeInsertIndex = findInsertIndex2(
569+
overlappingExistingIds,
570+
newEntities[i],
571+
comparer,
572+
lastRelativeInsertIndex,
573+
)
574+
if (lastRelativeInsertIndex !== relativeInsertIndex) {
575+
const insertIndex =
576+
startIndex + newIdIndexOfLastInsert + lastRelativeInsertIndex
577+
const arrayToInsert = newEntities.slice(newIdIndexOfLastInsert, i)
578+
existingSortedItems.splice(insertIndex, 0, ...arrayToInsert)
579+
newIdIndexOfLastInsert = i
580+
lastRelativeInsertIndex = relativeInsertIndex
581+
}
582+
}
583+
existingSortedItems.splice(
584+
startIndex + newIdIndexOfLastInsert + lastRelativeInsertIndex,
585+
0,
586+
...newEntities.slice(newIdIndexOfLastInsert),
587+
)
588+
} else if (updatedIds?.size) {
589+
existingSortedItems.sort(comparer)
590+
}
591+
592+
const newSortedIds = existingSortedItems.map(selectId)
593+
594+
if (!areArraysEqual(ids, newSortedIds)) {
595+
state.ids = newSortedIds
596+
}
597+
}
598+
502599
const mergeFunction: MergeFunction = mergeInsertion
503600

504601
function resortEntities(

packages/toolkit/src/entities/tests/sorted_state_adapter.test.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -592,8 +592,8 @@ describe('Sorted State Adapter', () => {
592592
})
593593

594594
it('should minimize the amount of sorting work needed', () => {
595-
const INITIAL_ITEMS = 100_000
596-
const ADDED_ITEMS = 1000
595+
const INITIAL_ITEMS = 10_000
596+
const ADDED_ITEMS = 1_000
597597

598598
type Entity = { id: string; name: string; position: number }
599599

@@ -663,6 +663,8 @@ describe('Sorted State Adapter', () => {
663663
store.dispatch(entitySlice.actions.upsertMany(initialItems))
664664
})
665665

666+
expect(numSorts).toBeLessThan(INITIAL_ITEMS * 20)
667+
666668
measureComparisons('Insert One (random)', () => {
667669
store.dispatch(
668670
entitySlice.actions.upsertOne({
@@ -673,6 +675,8 @@ describe('Sorted State Adapter', () => {
673675
)
674676
})
675677

678+
expect(numSorts).toBeLessThan(50)
679+
676680
measureComparisons('Insert One (middle)', () => {
677681
store.dispatch(
678682
entitySlice.actions.upsertOne({
@@ -683,6 +687,8 @@ describe('Sorted State Adapter', () => {
683687
)
684688
})
685689

690+
expect(numSorts).toBeLessThan(50)
691+
686692
measureComparisons('Insert One (end)', () => {
687693
store.dispatch(
688694
entitySlice.actions.upsertOne({
@@ -693,11 +699,15 @@ describe('Sorted State Adapter', () => {
693699
)
694700
})
695701

702+
expect(numSorts).toBeLessThan(50)
703+
696704
const addedItems = generateItems(ADDED_ITEMS)
697705
measureComparisons('Add Many', () => {
698706
store.dispatch(entitySlice.actions.addMany(addedItems))
699707
})
700708

709+
expect(numSorts).toBeLessThan(ADDED_ITEMS * 20)
710+
701711
// These numbers will vary because of the randomness, but generally
702712
// with 10K items the old code had 200K+ sort calls, while the new code
703713
// is around 130K sort calls.
@@ -718,6 +728,12 @@ describe('Sorted State Adapter', () => {
718728
)
719729
})
720730

731+
const SORTING_COUNT_BUFFER = 100
732+
733+
expect(numSorts).toBeLessThan(
734+
INITIAL_ITEMS + ADDED_ITEMS + SORTING_COUNT_BUFFER,
735+
)
736+
721737
measureComparisons('Update One (middle)', () => {
722738
store.dispatch(
723739
// Move this middle item near the end
@@ -730,6 +746,10 @@ describe('Sorted State Adapter', () => {
730746
)
731747
})
732748

749+
expect(numSorts).toBeLessThan(
750+
INITIAL_ITEMS + ADDED_ITEMS + SORTING_COUNT_BUFFER,
751+
)
752+
733753
measureComparisons('Update One (replace)', () => {
734754
store.dispatch(
735755
// Move this middle item near the end
@@ -743,6 +763,10 @@ describe('Sorted State Adapter', () => {
743763
)
744764
})
745765

766+
expect(numSorts).toBeLessThan(
767+
INITIAL_ITEMS + ADDED_ITEMS + SORTING_COUNT_BUFFER,
768+
)
769+
746770
// The old code was around 120K, the new code is around 10K.
747771
// expect(numSorts).toBeLessThan(25_000)
748772
})

0 commit comments

Comments
 (0)