From 741ed00bda22f3b445f5996a40ce4bd54a629cc6 Mon Sep 17 00:00:00 2001 From: David Sheldrick Date: Thu, 18 Apr 2024 08:57:37 +0100 Subject: [PATCH] [signia] Smart dirty checking of active computeds (#3516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a huge perf win, and it came to me while procrastinating on making dinner. The idea is that we can skip checking the parents of a computed value if - it is being dereferenced during a reaction cycle - the computed value was not traversed during the current reaction cycle This more than doubles the speed of the webgl minimap render on my machine (from 2ms down to like 0.8ms). This will make the biggest difference for anything that derives a value from a large collection of other computed values where typically only a small amount of them change at one time (e.g. iterating over all the shape page bounds to compile an RBush) Most code paths where we see a big chunk of `haveParentsChanged` in flame graphs should be much faster after this. ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `improvement` — Improving existing features --- packages/state/src/lib/core/Computed.ts | 13 ++++++++++--- packages/state/src/lib/core/transactions.ts | 4 ++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/state/src/lib/core/Computed.ts b/packages/state/src/lib/core/Computed.ts index 290f8c97d..2d82490d6 100644 --- a/packages/state/src/lib/core/Computed.ts +++ b/packages/state/src/lib/core/Computed.ts @@ -4,7 +4,7 @@ import { HistoryBuffer } from './HistoryBuffer' import { maybeCaptureParent, startCapturingParents, stopCapturingParents } from './capture' import { GLOBAL_START_EPOCH } from './constants' import { EMPTY_ARRAY, equals, haveParentsChanged, singleton } from './helpers' -import { getGlobalEpoch } from './transactions' +import { getGlobalEpoch, getIsReacting } from './transactions' import { Child, ComputeDiff, RESET_VALUE, Signal } from './types' import { logComputedGetterWarning } from './warnings' @@ -189,8 +189,15 @@ class __UNSAFE__Computed implements Computed __unsafe__getWithoutCapture(ignoreErrors?: boolean): Value { const isNew = this.lastChangedEpoch === GLOBAL_START_EPOCH - if (!isNew && (this.lastCheckedEpoch === getGlobalEpoch() || !haveParentsChanged(this))) { - this.lastCheckedEpoch = getGlobalEpoch() + const globalEpoch = getGlobalEpoch() + + if ( + !isNew && + (this.lastCheckedEpoch === globalEpoch || + (this.isActivelyListening && getIsReacting() && this.lastTraversedEpoch < globalEpoch) || + !haveParentsChanged(this)) + ) { + this.lastCheckedEpoch = globalEpoch if (this.error) { if (!ignoreErrors) { throw this.error.thrownValue diff --git a/packages/state/src/lib/core/transactions.ts b/packages/state/src/lib/core/transactions.ts index afb92d7d1..0e3672eee 100644 --- a/packages/state/src/lib/core/transactions.ts +++ b/packages/state/src/lib/core/transactions.ts @@ -70,6 +70,10 @@ export function getGlobalEpoch() { return inst.globalEpoch } +export function getIsReacting() { + return inst.globalIsReacting +} + /** * Collect all of the reactors that need to run for an atom and run them. *