diff --git a/benchmarking/benchmarks/reactivity/sbench.js b/benchmarking/benchmarks/reactivity/sbench.js index 196c29dc8d..47ff4a621a 100644 --- a/benchmarking/benchmarks/reactivity/sbench.js +++ b/benchmarking/benchmarks/reactivity/sbench.js @@ -37,7 +37,7 @@ function create_sbench_test(label, count, num_sources, fn) { fn(count, create_sources(num_sources, [])); } - const { timing } = await fastest_test(10, () => { + const { time, gc_time } = await fastest_test(10, () => { const destroy = $.effect_root(() => { for (let i = 0; i < 10; i++) { fn(count, create_sources(num_sources, [])); @@ -48,8 +48,8 @@ function create_sbench_test(label, count, num_sources, fn) { return { benchmark: label, - time: timing.time.toFixed(2), - gc_time: timing.gc_time.toFixed(2) + time: time.toFixed(2), + gc_time: gc_time.toFixed(2) }; }; } diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_avoidable.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_avoidable.bench.js index bb8c234f05..d4ba858824 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_avoidable.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_avoidable.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; import { busy } from '../util.js'; @@ -23,12 +23,12 @@ export default () => { $.flush(() => { $.set(head, 1); }); - assert($.get(computed5) === 6); + assert.equal($.get(computed5), 6); for (let i = 0; i < 1000; i++) { $.flush(() => { $.set(head, i); }); - assert($.get(computed5) === 6); + assert.equal($.get(computed5), 6); } } }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_broad.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_broad.bench.js index 14e855566b..aebae7a898 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_broad.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_broad.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; export default () => { @@ -33,9 +33,9 @@ export default () => { $.flush(() => { $.set(head, i); }); - assert($.get(last) === i + 50); + assert.equal($.get(last), i + 50); } - assert(counter === 50 * 50); + assert.equal(counter, 50 * 50); } }; }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_deep.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_deep.bench.js index 1051b0c1cf..4a361e9bfc 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_deep.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_deep.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; let len = 50; @@ -33,9 +33,9 @@ export default () => { $.flush(() => { $.set(head, i); }); - assert($.get(current) === len + i); + assert.equal($.get(current), len + i); } - assert(counter === iter); + assert.equal(counter, iter); } }; }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_diamond.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_diamond.bench.js index cde53fa3eb..17d9bd85e5 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_diamond.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_diamond.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; let width = 5; @@ -31,15 +31,15 @@ export default () => { $.flush(() => { $.set(head, 1); }); - assert($.get(sum) === 2 * width); + assert.equal($.get(sum), 2 * width); counter = 0; for (let i = 0; i < 500; i++) { $.flush(() => { $.set(head, i); }); - assert($.get(sum) === (i + 1) * width); + assert.equal($.get(sum), (i + 1) * width); } - assert(counter === 500); + assert.equal(counter, 500); } }; }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_mux.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_mux.bench.js index cfc7592c15..4af6bf7873 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_mux.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_mux.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; export default () => { @@ -25,13 +25,13 @@ export default () => { $.flush(() => { $.set(heads[i], i); }); - assert($.get(splited[i]) === i + 1); + assert.equal($.get(splited[i]), i + 1); } for (let i = 0; i < 10; i++) { $.flush(() => { $.set(heads[i], i * 2); }); - assert($.get(splited[i]) === i * 2 + 1); + assert.equal($.get(splited[i]), i * 2 + 1); } } }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_repeated.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_repeated.bench.js index 2887a39931..cab7689fea 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_repeated.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_repeated.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; let size = 30; @@ -28,15 +28,15 @@ export default () => { $.flush(() => { $.set(head, 1); }); - assert($.get(current) === size); + assert.equal($.get(current), size); counter = 0; for (let i = 0; i < 100; i++) { $.flush(() => { $.set(head, i); }); - assert($.get(current) === i * size); + assert.equal($.get(current), i * size); } - assert(counter === 100); + assert.equal(counter, 100); } }; }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_triangle.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_triangle.bench.js index 0abdd1e13d..b4b46c0209 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_triangle.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_triangle.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; let width = 10; @@ -41,15 +41,15 @@ export default () => { $.flush(() => { $.set(head, 1); }); - assert($.get(sum) === constant); + assert.equal($.get(sum), constant); counter = 0; for (let i = 0; i < 100; i++) { $.flush(() => { $.set(head, i); }); - assert($.get(sum) === constant - width + i * width); + assert.equal($.get(sum), constant - width + i * width); } - assert(counter === 100); + assert.equal(counter, 100); } }; }; diff --git a/benchmarking/benchmarks/reactivity/tests/kairo_unstable.bench.js b/benchmarking/benchmarks/reactivity/tests/kairo_unstable.bench.js index b09467326f..e7723fae0d 100644 --- a/benchmarking/benchmarks/reactivity/tests/kairo_unstable.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/kairo_unstable.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; export default () => { @@ -28,14 +28,14 @@ export default () => { $.flush(() => { $.set(head, 1); }); - assert($.get(current) === 40); + assert.equal($.get(current), 40); counter = 0; for (let i = 0; i < 100; i++) { $.flush(() => { $.set(head, i); }); } - assert(counter === 100); + assert.equal(counter, 100); } }; }; diff --git a/benchmarking/benchmarks/reactivity/tests/mol.bench.js b/benchmarking/benchmarks/reactivity/tests/mol.bench.js index 56b6dc8b28..e66f0191d1 100644 --- a/benchmarking/benchmarks/reactivity/tests/mol.bench.js +++ b/benchmarking/benchmarks/reactivity/tests/mol.bench.js @@ -1,4 +1,4 @@ -import { assert } from '../../../utils.js'; +import assert from 'node:assert'; import * as $ from 'svelte/internal/client'; /** @@ -59,7 +59,10 @@ export default () => { $.set(A, 2 + i * 2); $.set(B, 2); }); - assert(res[0] === 3198 && res[1] === 1601 && res[2] === 3195 && res[3] === 1598); + assert.equal(res[0], 3198); + assert.equal(res[1], 1601); + assert.equal(res[2], 3195); + assert.equal(res[3], 1598); } }; }; diff --git a/benchmarking/benchmarks/reactivity/util.js b/benchmarking/benchmarks/reactivity/util.js index 18c4f556af..ff0722d85e 100644 --- a/benchmarking/benchmarks/reactivity/util.js +++ b/benchmarking/benchmarks/reactivity/util.js @@ -25,7 +25,7 @@ export function create_test(label, setup) { const { run, destroy } = setup(); - const { timing } = await fastest_test(10, () => { + const { time, gc_time } = await fastest_test(10, () => { for (let i = 0; i < 1000; i++) { run(i); } @@ -35,8 +35,8 @@ export function create_test(label, setup) { return { benchmark: `${label}_unowned`, - time: timing.time.toFixed(2), - gc_time: timing.gc_time.toFixed(2) + time: time.toFixed(2), + gc_time: gc_time.toFixed(2) }; }, owned: async () => { @@ -53,7 +53,7 @@ export function create_test(label, setup) { ({ run, destroy } = setup()); }); - const { timing } = await fastest_test(10, () => { + const { time, gc_time } = await fastest_test(10, () => { for (let i = 0; i < 1000; i++) { run(i); } @@ -65,8 +65,8 @@ export function create_test(label, setup) { return { benchmark: `${label}_owned`, - time: timing.time.toFixed(2), - gc_time: timing.gc_time.toFixed(2) + time: time.toFixed(2), + gc_time: gc_time.toFixed(2) }; } }; diff --git a/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js b/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js index ba0457b80e..6fc992d4a2 100644 --- a/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js +++ b/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js @@ -1,13 +1,16 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { render } from 'svelte/server'; -import { fastest_test, read_file, write } from '../../../utils.js'; +import { fastest_test } from '../../../utils.js'; import { compile } from 'svelte/compiler'; const dir = `${process.cwd()}/benchmarking/benchmarks/ssr/wrapper`; async function compile_svelte() { - const output = compile(read_file(`${dir}/App.svelte`), { + const output = compile(read(`${dir}/App.svelte`), { generate: 'server' }); + write(`${dir}/output/App.js`, output.js.code); const module = await import(`${dir}/output/App.js`); @@ -17,12 +20,13 @@ async function compile_svelte() { export async function wrapper_bench() { const App = await compile_svelte(); + // Do 3 loops to warm up JIT for (let i = 0; i < 3; i++) { render(App); } - const { timing } = await fastest_test(10, () => { + const { time, gc_time } = await fastest_test(10, () => { for (let i = 0; i < 100; i++) { render(App); } @@ -30,7 +34,26 @@ export async function wrapper_bench() { return { benchmark: 'wrapper_bench', - time: timing.time.toFixed(2), - gc_time: timing.gc_time.toFixed(2) + time: time.toFixed(2), + gc_time: gc_time.toFixed(2) }; } + +/** + * @param {string} file + */ +function read(file) { + return fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'); +} + +/** + * @param {string} file + * @param {string} contents + */ +function write(file, contents) { + try { + fs.mkdirSync(path.dirname(file), { recursive: true }); + } catch {} + + fs.writeFileSync(file, contents); +} diff --git a/benchmarking/utils.js b/benchmarking/utils.js index 684d2ee02b..5581135e00 100644 --- a/benchmarking/utils.js +++ b/benchmarking/utils.js @@ -1,78 +1,30 @@ import { performance, PerformanceObserver } from 'node:perf_hooks'; import v8 from 'v8-natives'; -import * as fs from 'node:fs'; -import * as path from 'node:path'; // Credit to https://github.com/milomg/js-reactivity-benchmark for the logic for timing + GC tracking. -class GarbageTrack { - track_id = 0; - observer = new PerformanceObserver((list) => this.perf_entries.push(...list.getEntries())); - perf_entries = []; - periods = []; +async function track(fn) { + v8.collectGarbage(); - watch(fn) { - this.track_id++; - const start = performance.now(); - const result = fn(); - const end = performance.now(); - this.periods.push({ track_id: this.track_id, start, end }); + /** @type {PerformanceEntry[]} */ + const entries = []; - return { result, track_id: this.track_id }; - } + const observer = new PerformanceObserver((list) => entries.push(...list.getEntries())); + observer.observe({ entryTypes: ['gc'] }); - /** - * @param {number} track_id - */ - async gcDuration(track_id) { - await promise_delay(10); + const start = performance.now(); + fn(); + const end = performance.now(); - const period = this.periods.find((period) => period.track_id === track_id); - if (!period) { - // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors - return Promise.reject('no period found'); - } + await new Promise((f) => setTimeout(f, 10)); - const entries = this.perf_entries.filter( - (e) => e.startTime >= period.start && e.startTime < period.end - ); - return entries.reduce((t, e) => e.duration + t, 0); - } + const gc_time = entries + .filter((e) => e.startTime >= start && e.startTime < end) + .reduce((t, e) => e.duration + t, 0); - destroy() { - this.observer.disconnect(); - } + observer.disconnect(); - constructor() { - this.observer.observe({ entryTypes: ['gc'] }); - } -} - -function promise_delay(timeout = 0) { - return new Promise((resolve) => setTimeout(resolve, timeout)); -} - -/** - * @param {{ (): void; (): any; }} fn - */ -function run_timed(fn) { - const start = performance.now(); - const result = fn(); - const time = performance.now() - start; - return { result, time }; -} - -/** - * @param {() => void} fn - */ -async function run_tracked(fn) { - v8.collectGarbage(); - const gc_track = new GarbageTrack(); - const { result: wrappedResult, track_id } = gc_track.watch(() => run_timed(fn)); - const gc_time = await gc_track.gcDuration(track_id); - const { result, time } = wrappedResult; - gc_track.destroy(); - return { result, timing: { time, gc_time } }; + return { time: end - start, gc_time }; } /** @@ -80,40 +32,12 @@ async function run_tracked(fn) { * @param {() => void} fn */ export async function fastest_test(times, fn) { + /** @type {Array<{ time: number, gc_time: number }>} */ const results = []; - for (let i = 0; i < times; i++) { - const run = await run_tracked(fn); - results.push(run); - } - const fastest = results.reduce((a, b) => (a.timing.time < b.timing.time ? a : b)); - return fastest; -} - -/** - * @param {boolean} a - */ -export function assert(a) { - if (!a) { - throw new Error('Assertion failed'); + for (let i = 0; i < times; i++) { + results.push(await track(fn)); } -} - -/** - * @param {string} file - */ -export function read_file(file) { - return fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'); -} - -/** - * @param {string} file - * @param {string} contents - */ -export function write(file, contents) { - try { - fs.mkdirSync(path.dirname(file), { recursive: true }); - } catch {} - fs.writeFileSync(file, contents); + return results.reduce((a, b) => (a.time < b.time ? a : b)); }