mirror of https://github.com/sveltejs/svelte
chore: add a benchmarking tool + single benchmark (#12092)
* chore: add benchmarking tool and single benchmarkpull/12094/head
parent
a62dce3e83
commit
d04f69ac05
@ -0,0 +1,85 @@
|
|||||||
|
import { fastest_test } from '../utils.js';
|
||||||
|
import * as $ from '../../packages/svelte/src/internal/client/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
*/
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return 1;
|
||||||
|
return fib(n - 1) + fib(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} n
|
||||||
|
*/
|
||||||
|
function hard(n) {
|
||||||
|
return n + fib(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
const numbers = Array.from({ length: 5 }, (_, i) => i);
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
let res = [];
|
||||||
|
const A = $.source(0);
|
||||||
|
const B = $.source(0);
|
||||||
|
const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2));
|
||||||
|
const D = $.derived(() => numbers.map((i) => ({ x: i + ($.get(A) % 2) - ($.get(B) % 2) })));
|
||||||
|
const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0].x));
|
||||||
|
const F = $.derived(() => hard($.get(D)[2].x || $.get(B)));
|
||||||
|
const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[4].x + $.get(F));
|
||||||
|
|
||||||
|
const destroy = $.effect_root(() => {
|
||||||
|
$.render_effect(() => {
|
||||||
|
res.push(hard($.get(G)));
|
||||||
|
});
|
||||||
|
$.render_effect(() => {
|
||||||
|
res.push($.get(G));
|
||||||
|
});
|
||||||
|
$.render_effect(() => {
|
||||||
|
res.push(hard($.get(F)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy,
|
||||||
|
/**
|
||||||
|
* @param {number} i
|
||||||
|
*/
|
||||||
|
run(i) {
|
||||||
|
res.length = 0;
|
||||||
|
$.flush_sync(() => {
|
||||||
|
$.set(B, 1);
|
||||||
|
$.set(A, 1 + i * 2);
|
||||||
|
});
|
||||||
|
$.flush_sync(() => {
|
||||||
|
$.set(A, 2 + i * 2);
|
||||||
|
$.set(B, 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mol_bench() {
|
||||||
|
// Do 10 loops to warm up JIT
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const { run, destroy } = setup();
|
||||||
|
run(0);
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { run, destroy } = setup();
|
||||||
|
|
||||||
|
const { timing } = await fastest_test(10, () => {
|
||||||
|
for (let i = 0; i < 1e4; i++) {
|
||||||
|
run(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
destroy();
|
||||||
|
|
||||||
|
return {
|
||||||
|
benchmark: 'mol_bench',
|
||||||
|
time: timing.time.toFixed(2),
|
||||||
|
gc_time: timing.gc_time.toFixed(2)
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import * as $ from '../packages/svelte/src/internal/client/index.js';
|
||||||
|
import { mol_bench } from './benchmarks/mol_bench.js';
|
||||||
|
|
||||||
|
const benchmarks = [mol_bench];
|
||||||
|
|
||||||
|
async function run_benchmarks() {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
$.push({}, true);
|
||||||
|
for (const benchmark of benchmarks) {
|
||||||
|
results.push(await benchmark());
|
||||||
|
}
|
||||||
|
$.pop();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
run_benchmarks();
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true
|
||||||
|
},
|
||||||
|
"include": ["./run.js", "./utils.js", "./benchmarks"]
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
import { performance, PerformanceObserver } from 'node:perf_hooks';
|
||||||
|
import v8 from 'v8-natives';
|
||||||
|
|
||||||
|
// 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 = [];
|
||||||
|
|
||||||
|
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 });
|
||||||
|
|
||||||
|
return { result, track_id: this.track_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} track_id
|
||||||
|
*/
|
||||||
|
async gcDuration(track_id) {
|
||||||
|
await promise_delay(10);
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = this.perf_entries.filter(
|
||||||
|
(e) => e.startTime >= period.start && e.startTime < period.end
|
||||||
|
);
|
||||||
|
return entries.reduce((t, e) => e.duration + t, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.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 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} times
|
||||||
|
* @param {() => void} fn
|
||||||
|
*/
|
||||||
|
export async function fastest_test(times, fn) {
|
||||||
|
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;
|
||||||
|
}
|
Loading…
Reference in new issue