From c3ddbd3fdd3193ab05b07f1174d924e975e14e85 Mon Sep 17 00:00:00 2001 From: dangreen Date: Wed, 22 Jul 2026 00:53:06 +0400 Subject: [PATCH] perf(agera): speed up effect runs by splitting warmup and rerun paths Split `runEffect` into small `warmupEffect` and `runEffect` specializations so V8 can inline them into the effect creation and flush paths, and gate the `noMount` and `notifyMounted` lifecycle hooks behind `isMountableUsed`. Effect benchmark improves from ~345 to ~320 ns/op (~7% faster). Bump size limits by 10-20 B in `agera`, `kida` and `store` to account for the duplicated run bodies. --- packages/agera/.size-limit.json | 4 +-- packages/agera/src/effect.spec.ts | 2 +- packages/agera/src/internals/system.ts | 35 ++++++++++++++++++++------ packages/kida/.size-limit.json | 2 +- packages/store/.size-limit.json | 2 +- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/agera/.size-limit.json b/packages/agera/.size-limit.json index bcb3ca68..f8a0eda8 100644 --- a/packages/agera/.size-limit.json +++ b/packages/agera/.size-limit.json @@ -3,7 +3,7 @@ "name": "All publics", "path": "dist/index.js", "import": "*", - "limit": "2.46 kB" + "limit": "2.47 kB" }, { "name": "Signal", @@ -21,6 +21,6 @@ "name": "Popular set", "path": "dist/index.js", "import": "{ signal, computed, effect, mountable, onMounted }", - "limit": "1.87 kB" + "limit": "1.88 kB" } ] diff --git a/packages/agera/src/effect.spec.ts b/packages/agera/src/effect.spec.ts index a9b83b2d..091372b6 100644 --- a/packages/agera/src/effect.spec.ts +++ b/packages/agera/src/effect.spec.ts @@ -459,7 +459,7 @@ describe('agera', () => { $num(1) - expect(fn).toHaveBeenCalledWith(undefined) + expect(fn).toHaveBeenLastCalledWith() stop() }) diff --git a/packages/agera/src/internals/system.ts b/packages/agera/src/internals/system.ts index 0bc1b1af..14be07f6 100644 --- a/packages/agera/src/internals/system.ts +++ b/packages/agera/src/internals/system.ts @@ -523,7 +523,7 @@ export function effect(fn: EffectCallback, noDefer = false): Destroy { } } - runEffect(e, true) + warmupEffect(e) return effectOper.bind(e) } @@ -654,22 +654,41 @@ function updateSignal(s: SignalNode): boolean { return s.value !== (s.value = s.pendingValue) } -function runEffect(e: EffectNode, warmup?: true): void { +function warmupEffect(e: EffectNode): void { const prevSub = pushActiveSub(e) const prevNoMount = isMountableUsed ? pushNoMount(e.noMount) : undefined try { - e.destroy = e.fn(warmup) || undefined + e.destroy = e.fn(true) || undefined } finally { - popNoMount(prevNoMount) popActiveSub(prevSub) e.flags &= ~RecursedCheckFlag - if (warmup === undefined) { - purgeDeps(e) + if (isMountableUsed) { + popNoMount(prevNoMount) + notifyMounted(activeSub) } + } +} - notifyMounted(activeSub) +function runEffect(e: EffectNode): void { + const prevSub = pushActiveSub(e) + const prevNoMount = isMountableUsed ? pushNoMount(e.noMount) : undefined + + try { + e.destroy = e.fn() || undefined + } finally { + if (isMountableUsed) { + popNoMount(prevNoMount) + } + + popActiveSub(prevSub) + e.flags &= ~RecursedCheckFlag + purgeDeps(e) + + if (isMountableUsed) { + notifyMounted(activeSub) + } } } @@ -840,7 +859,7 @@ function runDeferredEffects(link: Link): void { runDeferredEffects(dep.deps) } } else { - runEffect(dep as EffectNode, true) + warmupEffect(dep as EffectNode) } } diff --git a/packages/kida/.size-limit.json b/packages/kida/.size-limit.json index 4ac877f0..3661744c 100644 --- a/packages/kida/.size-limit.json +++ b/packages/kida/.size-limit.json @@ -15,6 +15,6 @@ "name": "Popular set", "path": "dist/index.js", "import": "{ signal, record, computed, effect, mountable, onMount }", - "limit": "2.21 kB" + "limit": "2.23 kB" } ] diff --git a/packages/store/.size-limit.json b/packages/store/.size-limit.json index f17da55e..a2abdd5f 100644 --- a/packages/store/.size-limit.json +++ b/packages/store/.size-limit.json @@ -15,6 +15,6 @@ "name": "Popular set", "path": "dist/index.js", "import": "{ signal, record, computed, effect, mountable, onMount }", - "limit": "2.21 kB" + "limit": "2.23 kB" } ]