Skip to content

Instantly share code, notes, and snippets.

@peerreynders
Last active September 14, 2022 14:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save peerreynders/f2448bd7b35616beb476db0c09976dbf to your computer and use it in GitHub Desktop.
Save peerreynders/f2448bd7b35616beb476db0c09976dbf to your computer and use it in GitHub Desktop.
µsignal 0.4.2 performance comparison

Sample run on Ubuntu 22.04 LTS [i7-4900MQ CPU @ 2.80GHz/32GB] Chromium 105.0.5195.102

Solid (solid.html)

Created 205760 nodes.
278 ms; sum: 9.11303444731602e+305

@preact/signals (preactive.html)

Created 205760 nodes.
415 ms; sum: 9.11303444731602e+305

µsignal (usignal.html)

252 ms; sum: 9.11303444731602e+305
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>@preact/signals version</title>
<style></style>
</head>
<body>
<script type="module">
import {
signal,
computed,
effect,
} from 'https://unpkg.com/@preact/signals-core@1.1.0/dist/signals-core.module.js?module';
const fmt = new Intl.NumberFormat([], { maximumFractionDigits: 0 });
const GRAPH_DEPTH = 640;
function makeGraph(sources, depth) {
let nodes = 0;
for (; depth > 0; depth -= 1) {
const left = sources[0];
const right = sources[sources.length - 1];
const nextLevel = [computed(() => 2 * left.value)];
for (
let prev = sources[0], i = 1, current = sources[i];
i < sources.length;
prev = current, i += 1, current = sources[i]
) {
nextLevel[i] = computed(() => prev.value + current.value);
}
nextLevel[sources.length] = computed(() => 3 * right.value);
nodes += nextLevel.length;
sources = nextLevel;
}
console.log(`Created ${nodes} nodes.`);
return sources;
}
function setup() {
// create root signal
const source = signal(1);
const memos = makeGraph([source], GRAPH_DEPTH);
// consume top row of memos
let t0 = performance.now();
effect(() => {
const sum = memos.reduce((acc, memo) => acc + memo.value, 0);
const interval = performance.now() - t0;
console.log(`${fmt.format(interval)} ms; sum: ${sum}`);
});
return (newValue) => {
t0 = performance.now();
source.value = newValue;
};
}
(function run(fn, value) {
if (typeof fn === 'function' && typeof value === 'number') {
fn(value); // e.g. update(2)
return;
}
const update = setup();
setTimeout(run, 0, update, 2);
})();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>SolidJS version</title>
<style></style>
</head>
<body>
<script type="module">
import {
createEffect,
createMemo,
createSignal,
createRoot,
} from 'https://unpkg.com/solid-js@1.5.4/dist/solid.js?module';
const fmt = new Intl.NumberFormat([], { maximumFractionDigits: 0 });
const GRAPH_DEPTH = 640;
function makeGraph(sources, depth) {
let nodes = 0;
for (; depth > 0; depth -= 1) {
const left = sources[0];
const right = sources[sources.length - 1];
const nextLevel = [createMemo(() => 2 * left())];
for (
let prev = sources[0], i = 1, current = sources[i];
i < sources.length;
prev = current, i += 1, current = sources[i]
) {
nextLevel[i] = createMemo(() => prev() + current());
}
nextLevel[sources.length] = createMemo(() => 3 * right());
nodes += nextLevel.length;
sources = nextLevel;
}
console.log(`Created ${nodes} nodes.`);
return sources;
}
function setup() {
// create root signal
const [source, setSource] = createSignal(1);
const memos = makeGraph([source], GRAPH_DEPTH);
// consume top row of memos
let t0 = performance.now();
createEffect(() => {
const sum = memos.reduce((acc, memo) => acc + memo(), 0);
const interval = performance.now() - t0;
console.log(`${fmt.format(interval)} ms; sum: ${sum}`);
});
return (newValue) => {
t0 = performance.now();
setSource(newValue);
};
}
let dispose;
function run(fn, value) {
if (fn === undefined && dispose) {
dispose();
dispose = undefined;
return;
}
if (typeof fn !== 'function') return;
if (typeof value === 'number') {
fn(value); // e.g. update(2)
setTimeout(run);
return;
}
dispose = fn;
const update = setup();
setTimeout(run, 0, update, 2);
}
createRoot(run);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>µsignal version</title>
<style></style>
</head>
<body>
<script type="module">
import {
signal,
computed,
effect,
} from 'https://unpkg.com/usignal@0.4.2/esm/index.js?module';
const fmt = new Intl.NumberFormat([], { maximumFractionDigits: 0 });
const GRAPH_DEPTH = 640;
function makeGraph(sources, depth) {
let nodes = 0;
for (; depth > 0; depth -= 1) {
const left = sources[0];
const right = sources[sources.length - 1];
const nextLevel = [computed(() => 2 * left.value)];
for (
let prev = sources[0], i = 1, current = sources[i];
i < sources.length;
prev = current, i += 1, current = sources[i]
) {
nextLevel[i] = computed(() => prev.value + current.value);
}
nextLevel[sources.length] = computed(() => 3 * right.value);
nodes += nextLevel.length;
sources = nextLevel;
}
console.log(`Created ${nodes} nodes.`);
return sources;
}
function setup() {
// create root signal
const source = signal(1);
const memos = makeGraph([source], GRAPH_DEPTH);
// consume top row of memos
let t0 = performance.now();
effect(() => {
const sum = memos.reduce((acc, memo) => acc + memo.value, 0);
const interval = performance.now() - t0;
console.log(`${fmt.format(interval)} ms; sum: ${sum}`);
});
return (newValue) => {
t0 = performance.now();
source.value = newValue;
};
}
(function run(fn, value) {
if (typeof fn === 'function' && typeof value === 'number') {
fn(value); // e.g. update(2)
return;
}
const update = setup();
setTimeout(run, 0, update, 2);
})();
</script>
</body>
</html>
@WebReflection
Copy link

WebReflection commented Sep 14, 2022

usignal 0.4.x has async effect so if you want to compare apples with apples you need to use https://unpkg.com/usignal/esm/async.js?module as the async version is meant for browsers / DOM.

That is:

Created 350 nodes.
1 ms; sum: 1694963094854
14,308 ms; sum: 3389926189708

Te fact computed are lazy has not much to do with this benchmark but if this benchmark reveal an issue with usignal you are welcome to file an issue. I'll have a look at what you expect and what's different in usignal but you should start with updating the comparison using the async version.

edit I’m not sure I understand what I’m looking at … it feels like you are expecting all computed to side effect invokes of other computed so, in that regards, this demo shows usignal doesn’t do that, like the leak test shows it computes 2 times instead of 53 … do you have by any chance a minimal situation that shows usignal fails expectations? Not like debugging loops, you know … thanks.

@WebReflection
Copy link

@peerreynders please update those lame results on top by using https://unpkg.com/usignal/esm/async.js?module instead as import, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment