chore: opt for two signal data-structures to reduce memory usage

pull/9531/head
Dominic Gannaway 3 years ago
parent bbd1a6c05a
commit 5e4e2ac498

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: more signal perf tuning

@ -624,7 +624,7 @@ export function bind_playback_rate(media, get_value, update) {
// Needs to happen after the element is inserted into the dom, else playback will be set back to 1 by the browser. // Needs to happen after the element is inserted into the dom, else playback will be set back to 1 by the browser.
// For hydration we could do it immediately but the additional code is not worth the lost microtask. // For hydration we could do it immediately but the additional code is not worth the lost microtask.
/** @type {import('./types.js').Signal | undefined} */ /** @type {import('./types.js').ComputationSignal | undefined} */
let render; let render;
let destroyed = false; let destroyed = false;
const effect = managed_effect(() => { const effect = managed_effect(() => {
@ -2125,7 +2125,7 @@ export function destroy_each_item_block(
if (!controlled && dom !== null) { if (!controlled && dom !== null) {
remove(dom); remove(dom);
} }
destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect)); destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect));
} }
} }
@ -3163,7 +3163,7 @@ export function mount(component, options) {
if (hydration_fragment !== null) { if (hydration_fragment !== null) {
remove(hydration_fragment); remove(hydration_fragment);
} }
destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect)); destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect));
} }
]; ];
} }

@ -46,7 +46,7 @@ let current_queued_tasks = [];
let flush_count = 0; let flush_count = 0;
// Handle signal reactivity tree dependencies and consumer // Handle signal reactivity tree dependencies and consumer
/** @type {null | import('./types.js').Signal} */ /** @type {null | import('./types.js').ComputationSignal} */
let current_consumer = null; let current_consumer = null;
/** @type {null | import('./types.js').EffectSignal} */ /** @type {null | import('./types.js').EffectSignal} */
@ -133,14 +133,31 @@ function default_equals(a, b) {
return a === b; return a === b;
} }
/**
* @template V
* @param {import('./types.js').SignalFlags} flags
* @param {V} value
* @returns {import('./types.js').SourceSignal<V>}
*/
function create_source_signal(flags, value) {
return {
consumers: null,
// We can remove this if we get rid of beforeUpdate/afterUpdate
context: null,
equals: null,
flags,
value
};
}
/** /**
* @template V * @template V
* @param {import('./types.js').SignalFlags} flags * @param {import('./types.js').SignalFlags} flags
* @param {V} value * @param {V} value
* @param {import('./types.js').Block | null} block * @param {import('./types.js').Block | null} block
* @returns {import('./types.js').Signal<V>} * @returns {import('./types.js').ComputationSignal<V>}
*/ */
function create_signal_object(flags, value, block) { function create_computation_signal(flags, value, block) {
return { return {
block, block,
consumers: null, consumers: null,
@ -156,8 +173,8 @@ function create_signal_object(flags, value, block) {
} }
/** /**
* @param {import('./types.js').Signal} target_signal * @param {import('./types.js').ComputationSignal} target_signal
* @param {import('./types.js').Signal} ref_signal * @param {import('./types.js').ComputationSignal} ref_signal
* @returns {void} * @returns {void}
*/ */
function push_reference(target_signal, ref_signal) { function push_reference(target_signal, ref_signal) {
@ -180,7 +197,7 @@ function is_signal_dirty(signal) {
return true; return true;
} }
if ((flags & MAYBE_DIRTY) !== 0) { if ((flags & MAYBE_DIRTY) !== 0) {
const dependencies = signal.dependencies; const dependencies = /** @type {import('./types.js').ComputationSignal<V>} **/ (signal).dependencies;
if (dependencies !== null) { if (dependencies !== null) {
const length = dependencies.length; const length = dependencies.length;
let i; let i;
@ -195,7 +212,7 @@ function is_signal_dirty(signal) {
// The flags can be marked as dirty from the above is_signal_dirty call. // The flags can be marked as dirty from the above is_signal_dirty call.
if ((dependency.flags & DIRTY) !== 0) { if ((dependency.flags & DIRTY) !== 0) {
if ((dep_flags & DERIVED) !== 0) { if ((dep_flags & DERIVED) !== 0) {
update_derived(dependency, true); update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (dependency), true);
// Might have been mutated from above get. // Might have been mutated from above get.
if ((signal.flags & DIRTY) !== 0) { if ((signal.flags & DIRTY) !== 0) {
return true; return true;
@ -212,7 +229,7 @@ function is_signal_dirty(signal) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @returns {V} * @returns {V}
*/ */
function execute_signal_fn(signal) { function execute_signal_fn(signal) {
@ -247,7 +264,7 @@ function execute_signal_fn(signal) {
} else { } else {
res = /** @type {() => V} */ (init)(); res = /** @type {() => V} */ (init)();
} }
let dependencies = signal.dependencies; let dependencies = /** @type {import('./types.js').Signal<unknown>[]} **/ (signal.dependencies);
if (current_dependencies !== null) { if (current_dependencies !== null) {
let i; let i;
@ -259,7 +276,7 @@ function execute_signal_fn(signal) {
dependencies[current_dependencies_index + i] = current_dependencies[i]; dependencies[current_dependencies_index + i] = current_dependencies[i];
} }
} else { } else {
signal.dependencies = dependencies = current_dependencies; signal.dependencies = /** @type {import('./types.js').Signal<V>[]} **/ (dependencies = current_dependencies);
} }
if (!current_skip_consumer) { if (!current_skip_consumer) {
@ -291,7 +308,7 @@ function execute_signal_fn(signal) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @param {number} start_index * @param {number} start_index
* @param {boolean} remove_unowned * @param {boolean} remove_unowned
* @returns {void} * @returns {void}
@ -316,7 +333,11 @@ function remove_consumer(signal, start_index, remove_unowned) {
} }
} }
if (remove_unowned && consumers_length === 0 && (dependency.flags & UNOWNED) !== 0) { if (remove_unowned && consumers_length === 0 && (dependency.flags & UNOWNED) !== 0) {
remove_consumer(dependency, 0, true); remove_consumer(
/** @type {import('./types.js').ComputationSignal<V>} **/ (dependency),
0,
true
);
} }
} }
} }
@ -324,7 +345,7 @@ function remove_consumer(signal, start_index, remove_unowned) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @returns {void} * @returns {void}
*/ */
function destroy_references(signal) { function destroy_references(signal) {
@ -575,7 +596,7 @@ export async function tick() {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @param {boolean} force_schedule * @param {boolean} force_schedule
* @returns {void} * @returns {void}
*/ */
@ -615,10 +636,11 @@ export function store_get(store, store_name, stores) {
value: source(UNINITIALIZED), value: source(UNINITIALIZED),
unsubscribe: EMPTY_FUNC unsubscribe: EMPTY_FUNC
}; };
push_destroy_fn(entry.value, () => { // TODO: can we remove this code? it was refactored out when we split up source/comptued signals
/** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value = // push_destroy_fn(entry.value, () => {
/** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value; // /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value =
}); // /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value;
// });
stores[store_name] = entry; stores[store_name] = entry;
} }
@ -676,7 +698,8 @@ export function unsubscribe_on_destroy(stores) {
for (store_name in stores) { for (store_name in stores) {
const ref = stores[store_name]; const ref = stores[store_name];
ref.unsubscribe(); ref.unsubscribe();
destroy_signal(ref.value); // TODO: can we remove this code? it was refactored out when we split up source/comptued signals
// destroy_signal(ref.value);
} }
}); });
} }
@ -740,7 +763,7 @@ export function get(signal) {
} }
if ((flags & DERIVED) !== 0 && is_signal_dirty(signal)) { if ((flags & DERIVED) !== 0 && is_signal_dirty(signal)) {
update_derived(signal, false); update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (signal), false);
} }
return signal.value; return signal.value;
} }
@ -845,7 +868,7 @@ export function mutate_store(store, expression, new_value) {
} }
/** /**
* @param {import('./types.js').Signal} signal * @param {import('./types.js').ComputationSignal} signal
* @param {boolean} inert * @param {boolean} inert
* @returns {void} * @returns {void}
*/ */
@ -969,12 +992,13 @@ export function set_signal_value(signal, value) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @returns {void} * @returns {void}
*/ */
export function destroy_signal(signal) { export function destroy_signal(signal) {
const teardown = /** @type {null | (() => void)} */ (signal.value); const teardown = /** @type {null | (() => void)} */ (signal.value);
const destroy = signal.destroy; const destroy = signal.destroy;
const flags = signal.flags;
destroy_references(signal); destroy_references(signal);
remove_consumer(signal, 0, true); remove_consumer(signal, 0, true);
signal.init = null; signal.init = null;
@ -1005,14 +1029,14 @@ export function destroy_signal(signal) {
* @template V * @template V
* @param {() => V} init * @param {() => V} init
* @param {import('./types.js').EqualsFunctions} [equals] * @param {import('./types.js').EqualsFunctions} [equals]
* @returns {import('./types.js').Signal<V>} * @returns {import('./types.js').ComputationSignal<V>}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function derived(init, equals) { export function derived(init, equals) {
const is_unowned = current_effect === null; const is_unowned = current_effect === null;
const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; const flags = is_unowned ? DERIVED | UNOWNED : DERIVED;
const signal = /** @type {import('./types.js').Signal<V>} */ ( const signal = /** @type {import('./types.js').ComputationSignal<V>} */ (
create_signal_object(flags | CLEAN, UNINITIALIZED, current_block) create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
); );
signal.init = init; signal.init = init;
signal.context = current_component_context; signal.context = current_component_context;
@ -1027,11 +1051,11 @@ export function derived(init, equals) {
* @template V * @template V
* @param {V} initial_value * @param {V} initial_value
* @param {import('./types.js').EqualsFunctions<V>} [equals] * @param {import('./types.js').EqualsFunctions<V>} [equals]
* @returns {import('./types.js').Signal<V>} * @returns {import('./types.js').SourceSignal<V>}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function source(initial_value, equals) { export function source(initial_value, equals) {
const source = create_signal_object(SOURCE | CLEAN, initial_value, null); const source = create_source_signal(SOURCE | CLEAN, initial_value);
source.context = current_component_context; source.context = current_component_context;
source.equals = get_equals_method(equals); source.equals = get_equals_method(equals);
return source; return source;
@ -1079,7 +1103,7 @@ export function untrack(fn) {
* @returns {import('./types.js').EffectSignal} * @returns {import('./types.js').EffectSignal}
*/ */
function internal_create_effect(type, init, sync, block, schedule) { function internal_create_effect(type, init, sync, block, schedule) {
const signal = create_signal_object(type | DIRTY, null, block); const signal = create_computation_signal(type | DIRTY, null, block);
signal.init = init; signal.init = init;
signal.context = current_component_context; signal.context = current_component_context;
if (schedule) { if (schedule) {
@ -1210,7 +1234,7 @@ export function managed_render_effect(init, block = current_block, sync = true)
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @param {() => void} destroy_fn * @param {() => void} destroy_fn
* @returns {void} * @returns {void}
*/ */

@ -253,7 +253,7 @@ function handle_raf(time) {
* @param {HTMLElement} dom * @param {HTMLElement} dom
* @param {() => import('./types.js').TransitionPayload} init * @param {() => import('./types.js').TransitionPayload} init
* @param {'in' | 'out' | 'both' | 'key'} direction * @param {'in' | 'out' | 'both' | 'key'} direction
* @param {import('./types.js').Signal<unknown>} effect * @param {import('./types.js').EffectSignal} effect
* @returns {import('./types.js').Transition} * @returns {import('./types.js').Transition}
*/ */
function create_transition(dom, init, direction, effect) { function create_transition(dom, init, direction, effect) {

@ -46,15 +46,28 @@ export type ComponentContext = {
}; };
}; };
export type Signal<V = unknown> = { export type SourceSignal<V = unknown> = {
/** Signals that read from the current signal */
consumers: null | ComputationSignal[];
/** The associated component if this signal is an effect/computed */
context: null | ComponentContext;
/** For value equality */
equals: null | EqualsFunctions;
/** The types that the signal represent, as a bitwise value */
flags: SignalFlags;
/** The latest value for this signal, doubles as the teardown for effects */
value: V;
};
export type ComputationSignal<V = unknown> = {
/** The block associated with this effect/computed */ /** The block associated with this effect/computed */
block: null | Block; block: null | Block;
/** Signals that read from the current signal */ /** Signals that read from the current signal */
consumers: null | Signal[]; consumers: null | ComputationSignal[];
/** The associated component if this signal is an effect/computed */ /** The associated component if this signal is an effect/computed */
context: null | ComponentContext; context: null | ComponentContext;
/** Signals that this signal reads from */ /** Signals that this signal reads from */
dependencies: null | Signal[]; dependencies: null | Signal<V>[];
/** Thing(s) that need destroying */ /** Thing(s) that need destroying */
destroy: null | (() => void) | Array<() => void>; destroy: null | (() => void) | Array<() => void>;
/** For value equality */ /** For value equality */
@ -64,12 +77,14 @@ export type Signal<V = unknown> = {
/** The function that we invoke for effects and computeds */ /** The function that we invoke for effects and computeds */
init: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void)); init: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void));
/** Anything that a signal owns */ /** Anything that a signal owns */
references: null | Signal[]; references: null | ComputationSignal[];
/** The latest value for this signal, doubles as the teardown for effects */ /** The latest value for this signal, doubles as the teardown for effects */
value: V; value: V;
}; };
export type EffectSignal = Signal<null | (() => void)>; export type Signal<V = unknown> = SourceSignal<V> | ComputationSignal<V>;
export type EffectSignal = ComputationSignal<null | (() => void)>;
export type MaybeSignal<T = unknown> = T | Signal<T>; export type MaybeSignal<T = unknown> = T | Signal<T>;
@ -92,7 +107,7 @@ export type BlockType =
export type TemplateNode = Text | Element | Comment; export type TemplateNode = Text | Element | Comment;
export type Transition = { export type Transition = {
effect: Signal; effect: EffectSignal;
payload: null | TransitionPayload; payload: null | TransitionPayload;
init: (from?: DOMRect) => TransitionPayload; init: (from?: DOMRect) => TransitionPayload;
finished: (fn: () => void) => void; finished: (fn: () => void) => void;
@ -106,7 +121,7 @@ export type Transition = {
export type RootBlock = { export type RootBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
container: Node; container: Node;
intro: boolean; intro: boolean;
parent: null; parent: null;
@ -117,7 +132,7 @@ export type RootBlock = {
export type IfBlock = { export type IfBlock = {
current: boolean; current: boolean;
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof IF_BLOCK; type: typeof IF_BLOCK;
@ -125,7 +140,7 @@ export type IfBlock = {
export type KeyBlock = { export type KeyBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof KEY_BLOCK; type: typeof KEY_BLOCK;
@ -133,7 +148,7 @@ export type KeyBlock = {
export type HeadBlock = { export type HeadBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof HEAD_BLOCK; type: typeof HEAD_BLOCK;
@ -141,7 +156,7 @@ export type HeadBlock = {
export type DynamicElementBlock = { export type DynamicElementBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof DYNAMIC_ELEMENT_BLOCK; type: typeof DYNAMIC_ELEMENT_BLOCK;
@ -149,7 +164,7 @@ export type DynamicElementBlock = {
export type DynamicComponentBlock = { export type DynamicComponentBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof DYNAMIC_COMPONENT_BLOCK; type: typeof DYNAMIC_COMPONENT_BLOCK;
@ -157,7 +172,7 @@ export type DynamicComponentBlock = {
export type AwaitBlock = { export type AwaitBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
pending: boolean; pending: boolean;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
@ -169,7 +184,7 @@ export type EachBlock = {
flags: number; flags: number;
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
items: EachItemBlock[]; items: EachItemBlock[];
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
transitions: Array<EachItemBlock>; transitions: Array<EachItemBlock>;
@ -178,7 +193,7 @@ export type EachBlock = {
export type EachItemBlock = { export type EachItemBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
item: any | Signal<any>; item: any | Signal<any>;
index: number | Signal<number>; index: number | Signal<number>;
key: unknown; key: unknown;
@ -191,7 +206,7 @@ export type EachItemBlock = {
export type SnippetBlock = { export type SnippetBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
parent: Block; parent: Block;
effect: null | Signal; effect: null | ComputationSignal;
transition: null; transition: null;
type: typeof SNIPPET_BLOCK; type: typeof SNIPPET_BLOCK;
}; };

Loading…
Cancel
Save