Skip to content

Commit

Permalink
perf(core): optimize multiple sequential reads of the same signal
Browse files Browse the repository at this point in the history
This commit adds an optimization to the signals graph to avoid adding multiple
producer records to a consumer when the same signal is read several times in a
row. This can happen when state is stored in a larger signal which is repeatedly
read to access different individual properties.
  • Loading branch information
alxhub committed Dec 10, 2024
1 parent 46f00f9 commit 903dc6b
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 3 deletions.
12 changes: 9 additions & 3 deletions packages/core/primitives/signals/src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,17 @@ export function producerAccessed(node: ReactiveNode): void {
}

activeConsumer.consumerOnSignalRead(node);
assertConsumerNode(activeConsumer);

// This producer is the `idx`th dependency of `activeConsumer`.
const idx = activeConsumer.nextProducerIndex++;
// Check if this producer is the tail dependency of `activeConsumer`. This is a cheap optimization
// for cases when the same signal is read back-to-back.
let idx = activeConsumer.producerNode.length - 1;
if (idx >= 0 && activeConsumer.producerNode[idx] === node) {
return;
}

assertConsumerNode(activeConsumer);
// This producer is the `idx`th dependency of `activeConsumer`.
idx = activeConsumer.nextProducerIndex++;

if (idx < activeConsumer.producerNode.length && activeConsumer.producerNode[idx] !== node) {
// There's been a change in producers since the last execution of `activeConsumer`.
Expand Down
13 changes: 13 additions & 0 deletions packages/core/test/signals/signal_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ describe('signals', () => {
expect(derived()).toBe(2);
expect(computations).toBe(2);
});

it('should coalesce back-to-back reads of the same signal', () => {
const source = signal(1);
const derived = computed(() => source() + source() + source());
const derivedNode = derived[SIGNAL] as ReactiveNode;

expect(derived()).toBe(3);
expect(derivedNode.producerNode?.length).toBe(1);

source.set(2);
expect(derived()).toBe(6);
expect(derivedNode.producerNode?.length).toBe(1);
});
});

describe('post-signal-set functions', () => {
Expand Down

0 comments on commit 903dc6b

Please sign in to comment.