From b225ac763594fa7d38a00d908144cc0fd9fe88aa Mon Sep 17 00:00:00 2001 From: skirtle <65301168+skirtles-code@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:32:13 +0100 Subject: [PATCH] perf(reactivity): avoid unnecessary recursion in removeSub --- .../reactivity/__tests__/computed.spec.ts | 32 +++++++++++++++++++ packages/reactivity/src/effect.ts | 25 ++++++++------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 36c477adb88..123df44f253 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/packages/reactivity/__tests__/computed.spec.ts @@ -1107,4 +1107,36 @@ describe('reactivity/computed', () => { end.prop4.value, ]).toMatchObject([-2, -4, 2, 3]) }) + + test('performance when removing dependencies from deeply nested computeds', () => { + const base = ref(1) + const trigger = ref(true) + const computeds: ComputedRef[] = [] + + const LAYERS = 30 + + for (let i = 0; i < LAYERS; i++) { + const earlier = [...computeds] + + computeds.push( + computed(() => { + return base.value + earlier.reduce((sum, c) => sum + c.value, 0) + }), + ) + } + + const tail = computed(() => + trigger.value ? computeds[computeds.length - 1].value : 0, + ) + + const t0 = performance.now() + expect(tail.value).toBe(2 ** (LAYERS - 1)) + const t1 = performance.now() + expect(t1 - t0).toBeLessThan(process.env.CI ? 100 : 30) + + trigger.value = false + expect(tail.value).toBe(0) + const t2 = performance.now() + expect(t2 - t1).toBeLessThan(process.env.CI ? 100 : 30) + }) }) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 243518839b3..886f380dd52 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -426,23 +426,24 @@ function removeSub(link: Link, soft = false) { nextSub.prevSub = prevSub link.nextSub = undefined } - if (dep.subs === link) { - // was previous tail, point new tail to prev - dep.subs = prevSub - } if (__DEV__ && dep.subsHead === link) { // was previous head, point new head to next dep.subsHead = nextSub } - if (!dep.subs && dep.computed) { - // if computed, unsubscribe it from all its deps so this computed and its - // value can be GCed - dep.computed.flags &= ~EffectFlags.TRACKING - for (let l = dep.computed.deps; l; l = l.nextDep) { - // here we are only "soft" unsubscribing because the computed still keeps - // referencing the deps and the dep should not decrease its sub count - removeSub(l, true) + if (dep.subs === link) { + // was previous tail, point new tail to prev + dep.subs = prevSub + + if (!prevSub && dep.computed) { + // if computed, unsubscribe it from all its deps so this computed and its + // value can be GCed + dep.computed.flags &= ~EffectFlags.TRACKING + for (let l = dep.computed.deps; l; l = l.nextDep) { + // here we are only "soft" unsubscribing because the computed still keeps + // referencing the deps and the dep should not decrease its sub count + removeSub(l, true) + } } }