0
0
mirror of https://github.com/sveltejs/svelte.git synced 2024-11-24 00:07:30 +01:00

chore: add a benchmarking tool + single benchmark (#12092)

* chore: add benchmarking tool and single benchmark
This commit is contained in:
Dominic Gannaway 2024-06-19 18:33:53 +01:00 committed by GitHub
parent a62dce3e83
commit d04f69ac05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 237 additions and 15 deletions

View File

@ -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)
};
}

19
benchmarking/run.js Normal file
View File

@ -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();

View File

@ -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"]
}

89
benchmarking/utils.js Normal file
View File

@ -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;
}

View File

@ -23,7 +23,9 @@
"test": "vitest run",
"test-output": "vitest run --coverage --reporter=json --outputFile=sites/svelte-5-preview/src/routes/status/results.json",
"changeset:version": "changeset version && pnpm -r generate:version && git add --all",
"changeset:publish": "changeset publish"
"changeset:publish": "changeset publish",
"bench": "node --allow-natives-syntax ./benchmarking/run.js",
"bench:debug": "node --allow-natives-syntax --inspect-brk ./benchmarking/run.js"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
@ -41,6 +43,7 @@
"prettier-plugin-svelte": "^3.1.2",
"typescript": "^5.3.3",
"typescript-eslint": "^8.0.0-alpha.20",
"v8-natives": "^1.2.5",
"vitest": "^1.2.1"
},
"pnpm": {

View File

@ -16,7 +16,7 @@ importers:
version: 2.27.1
'@sveltejs/eslint-config':
specifier: ^7.0.1
version: 7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3)
version: 7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3)
'@svitejs/changesets-changelog-github-compact':
specifier: ^1.1.0
version: 1.1.0
@ -49,13 +49,16 @@ importers:
version: 3.2.4
prettier-plugin-svelte:
specifier: ^3.1.2
version: 3.1.2(prettier@3.2.4)(svelte@5.0.0-next.144)
version: 3.1.2(prettier@3.2.4)(svelte@5.0.0-next.158)
typescript:
specifier: ^5.3.3
version: 5.3.3
typescript-eslint:
specifier: ^8.0.0-alpha.20
version: 8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3)
v8-natives:
specifier: ^1.2.5
version: 1.2.5
vitest:
specifier: ^1.2.1
version: 1.2.1(@types/node@20.11.5)(jsdom@22.0.0)(lightningcss@1.23.0)(sass@1.70.0)(terser@5.27.0)
@ -4575,8 +4578,8 @@ packages:
resolution: {integrity: sha512-hsoB/WZGEPFXeRRLPhPrbRz67PhP6sqYgvwcAs+gWdSQSvNDw+/lTeUJSWe5h2xC97Fz/8QxAOqItwBzNJPU8w==}
engines: {node: '>=16'}
svelte@5.0.0-next.144:
resolution: {integrity: sha512-akjtRBHzaLa1XdMv9tBGkXE5N2JaRc3gL+ZIctjc9Gew9DF7NxGTlxXq+HR9yUV7Lsg4o9ltMfkxz8H3K7piNQ==}
svelte@5.0.0-next.158:
resolution: {integrity: sha512-QRmXxHByWntyWqLtzjNsBbNT89F2yA7aWPp9M9l9a6/PAE3gmQh6+qoVPgrxMR7iiFgpwh5ZU9Bm25j3IhGicQ==}
engines: {node: '>=18'}
symbol-tree@3.2.4:
@ -4829,6 +4832,10 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
v8-natives@1.2.5:
resolution: {integrity: sha512-CVNliz6KF2yet3HBIkbFJKZmjlt95C8dsNZDnwoS6X98+QJRpsSz9uxo3TziBqdyJQkWwfD3VG9lRzsQNvF24Q==}
engines: {node: '>= 0.6.0'}
v8-to-istanbul@9.2.0:
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
engines: {node: '>=10.12.0'}
@ -6453,12 +6460,12 @@ snapshots:
- encoding
- supports-color
'@sveltejs/eslint-config@7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3)':
'@sveltejs/eslint-config@7.0.1(@stylistic/eslint-plugin-js@1.8.0(eslint@9.0.0))(eslint-config-prettier@9.1.0(eslint@9.0.0))(eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158))(eslint-plugin-unicorn@52.0.0(eslint@9.0.0))(eslint@9.0.0)(typescript-eslint@8.0.0-alpha.20(eslint@9.0.0)(typescript@5.3.3))(typescript@5.3.3)':
dependencies:
'@stylistic/eslint-plugin-js': 1.8.0(eslint@9.0.0)
eslint: 9.0.0
eslint-config-prettier: 9.1.0(eslint@9.0.0)
eslint-plugin-svelte: 2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144)
eslint-plugin-svelte: 2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158)
eslint-plugin-unicorn: 52.0.0(eslint@9.0.0)
globals: 15.0.0
typescript: 5.3.3
@ -7570,7 +7577,7 @@ snapshots:
eslint-plugin-lube@0.4.3: {}
eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.144):
eslint-plugin-svelte@2.38.0(eslint@9.0.0)(svelte@5.0.0-next.158):
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.0.0)
'@jridgewell/sourcemap-codec': 1.4.15
@ -7584,9 +7591,9 @@ snapshots:
postcss-safe-parser: 6.0.0(postcss@8.4.38)
postcss-selector-parser: 6.0.16
semver: 7.6.0
svelte-eslint-parser: 0.35.0(svelte@5.0.0-next.144)
svelte-eslint-parser: 0.35.0(svelte@5.0.0-next.158)
optionalDependencies:
svelte: 5.0.0-next.144
svelte: 5.0.0-next.158
transitivePeerDependencies:
- supports-color
- ts-node
@ -9094,10 +9101,10 @@ snapshots:
prettier: 3.2.4
svelte: 4.2.9
prettier-plugin-svelte@3.1.2(prettier@3.2.4)(svelte@5.0.0-next.144):
prettier-plugin-svelte@3.1.2(prettier@3.2.4)(svelte@5.0.0-next.158):
dependencies:
prettier: 3.2.4
svelte: 5.0.0-next.144
svelte: 5.0.0-next.158
prettier@2.8.8: {}
@ -9726,7 +9733,7 @@ snapshots:
- stylus
- sugarss
svelte-eslint-parser@0.35.0(svelte@5.0.0-next.144):
svelte-eslint-parser@0.35.0(svelte@5.0.0-next.158):
dependencies:
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
@ -9734,7 +9741,7 @@ snapshots:
postcss: 8.4.38
postcss-scss: 4.0.9(postcss@8.4.38)
optionalDependencies:
svelte: 5.0.0-next.144
svelte: 5.0.0-next.158
svelte-hmr@0.16.0(svelte@4.2.9):
dependencies:
@ -9809,7 +9816,7 @@ snapshots:
magic-string: 0.30.5
periscopic: 3.1.0
svelte@5.0.0-next.144:
svelte@5.0.0-next.158:
dependencies:
'@ampproject/remapping': 2.2.1
'@jridgewell/sourcemap-codec': 1.4.15
@ -10070,6 +10077,8 @@ snapshots:
utils-merge@1.0.1: {}
v8-natives@1.2.5: {}
v8-to-istanbul@9.2.0:
dependencies:
'@jridgewell/trace-mapping': 0.3.22