chore: add more benchmarks (#12094)

chore: add more benchmarks
pull/12096/head
Dominic Gannaway 6 months ago committed by GitHub
parent 60e19c7c04
commit c7f6805c37
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -60,3 +60,17 @@ jobs:
- name: build and check generated types - name: build and check generated types
if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail if: (${{ success() }} || ${{ failure() }}) # ensures this step runs even if previous steps fail
run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); } run: pnpm build && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please regenerate types locally and commit the changes after you have reviewed them"; git diff; exit 1); }
Benchmarks:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v3
with:
node-version: 18
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm bench
env:
CI: true

@ -0,0 +1,60 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
import { busy } from './util.js';
function setup() {
let head = $.source(0);
let computed1 = $.derived(() => $.get(head));
let computed2 = $.derived(() => ($.get(computed1), 0));
let computed3 = $.derived(() => (busy(), $.get(computed2) + 1)); // heavy computation
let computed4 = $.derived(() => $.get(computed3) + 2);
let computed5 = $.derived(() => $.get(computed4) + 3);
const destroy = $.effect_root(() => {
$.render_effect(() => {
$.get(computed5);
busy(); // heavy side effect
});
});
return {
destroy,
run() {
$.flush_sync(() => {
$.set(head, 1);
});
assert($.get(computed5) === 6);
for (let i = 0; i < 1000; i++) {
$.flush_sync(() => {
$.set(head, i);
});
assert($.get(computed5) === 6);
}
}
};
}
export async function kairo_avoidable() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_avoidable',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,66 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
function setup() {
let head = $.source(0);
let last = head;
let counter = 0;
const destroy = $.effect_root(() => {
for (let i = 0; i < 50; i++) {
let current = $.derived(() => {
return $.get(head) + i;
});
let current2 = $.derived(() => {
return $.get(current) + 1;
});
$.render_effect(() => {
$.get(current2);
counter++;
});
last = current2;
}
});
return {
destroy,
run() {
$.flush_sync(() => {
$.set(head, 1);
});
counter = 0
for (let i = 0; i < 50; i++) {
$.flush_sync(() => {
$.set(head, i);
});
assert($.get(last) === i + 50);
}
assert(counter === 50 * 50);
}
};
}
export async function kairo_broad() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_broad',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,66 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
let len = 50;
const iter = 50;
function setup() {
let head = $.source(0);
let current = head;
for (let i = 0; i < len; i++) {
let c = current;
current = $.derived(() => {
return $.get(c) + 1;
});
}
let counter = 0;
const destroy = $.effect_root(() => {
$.render_effect(() => {
$.get(current);
counter++;
});
});
return {
destroy,
run() {
$.flush_sync(() => {
$.set(head, 1);
});
counter = 0
for (let i = 0; i < iter; i++) {
$.flush_sync(() => {
$.set(head, i);
});
assert($.get(current) === len + i);
}
assert(counter === iter);
}
};
}
export async function kairo_deep() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_deep',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,70 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
let width = 5;
function setup() {
let head = $.source(0);
let current = [];
for (let i = 0; i < width; i++) {
current.push(
$.derived(() => {
return $.get(head) + 1;
})
);
}
let sum = $.derived(() => {
return current.map((x) => $.get(x)).reduce((a, b) => a + b, 0);
});
let counter = 0;
const destroy = $.effect_root(() => {
$.render_effect(() => {
$.get(sum);
counter++;
});
});
return {
destroy,
run() {
$.flush_sync(() => {
$.set(head, 1);
});
assert($.get(sum) === 2 * width);
counter = 0;
for (let i = 0; i < 500; i++) {
$.flush_sync(() => {
$.set(head, i);
});
assert($.get(sum) === (i + 1) * width);
}
assert(counter === 500);
}
};
}
export async function kairo_diamond() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_diamond',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,63 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
function setup() {
let heads = new Array(100).fill(null).map((_) => $.source(0));
const mux = $.derived(() => {
return Object.fromEntries(heads.map((h) => $.get(h)).entries());
});
const splited = heads
.map((_, index) => $.derived(() => $.get(mux)[index]))
.map((x) => $.derived(() => $.get(x) + 1));
const destroy = $.effect_root(() => {
splited.forEach((x) => {
$.render_effect(() => {
$.get(x);
});
});
});
return {
destroy,
run() {
for (let i = 0; i < 10; i++) {
$.flush_sync(() => {
$.set(heads[i], i);
});
assert($.get(splited[i]) === i + 1);
}
for (let i = 0; i < 10; i++) {
$.flush_sync(() => {
$.set(heads[i], i * 2);
});
assert($.get(splited[i]) === i * 2 + 1);
}
}
};
}
export async function kairo_mux() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_mux',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,67 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
let size = 30;
function setup() {
let head = $.source(0);
let current = $.derived(() => {
let result = 0;
for (let i = 0; i < size; i++) {
result += $.get(head);
}
return result;
});
let counter = 0;
const destroy = $.effect_root(() => {
$.render_effect(() => {
$.get(current);
counter++;
});
});
return {
destroy,
run() {
$.flush_sync(() => {
$.set(head, 1);
});
assert($.get(current) === size);
counter = 0;
for (let i = 0; i < 100; i++) {
$.flush_sync(() => {
$.set(head, i);
});
assert($.get(current) === i * size);
}
assert(counter === 100);
}
};
}
export async function kairo_repeated() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_repeated',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,80 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
let width = 10;
function count(number) {
return new Array(number)
.fill(0)
.map((_, i) => i + 1)
.reduce((x, y) => x + y, 0);
}
function setup() {
let head = $.source(0);
let current = head;
let list = [];
for (let i = 0; i < width; i++) {
let c = current;
list.push(current);
current = $.derived(() => {
return $.get(c) + 1;
});
}
let sum = $.derived(() => {
return list.map((x) => $.get(x)).reduce((a, b) => a + b, 0);
});
let counter = 0;
const destroy = $.effect_root(() => {
$.render_effect(() => {
$.get(sum);
counter++;
});
});
return {
destroy,
run() {
const constant = count(width);
$.flush_sync(() => {
$.set(head, 1);
});
assert($.get(sum) === constant);
counter = 0;
for (let i = 0; i < 100; i++) {
$.flush_sync(() => {
$.set(head, i);
});
assert($.get(sum) === constant - width + i * width);
}
assert(counter === 100);
}
};
}
export async function kairo_triangle() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_triangle',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,66 @@
import { assert, fastest_test } from '../../utils.js';
import * as $ from '../../../packages/svelte/src/internal/client/index.js';
function setup() {
let head = $.source(0);
const double = $.derived(() => $.get(head) * 2);
const inverse = $.derived(() => -$.get(head));
let current = $.derived(() => {
let result = 0;
for (let i = 0; i < 20; i++) {
result += $.get(head) % 2 ? $.get(double) : $.get(inverse);
}
return result;
});
let counter = 0;
const destroy = $.effect_root(() => {
$.render_effect(() => {
$.get(current);
counter++;
});
});
return {
destroy,
run() {
$.flush_sync(() => {
$.set(head, 1);
});
assert($.get(current) === 40);
counter = 0;
for (let i = 0; i < 100; i++) {
$.flush_sync(() => {
$.set(head, i);
});
}
assert(counter === 100);
}
};
}
export async function kairo_unstable() {
// Do 10 loops to warm up JIT
for (let i = 0; i < 10; i++) {
const { run, destroy } = setup();
run();
destroy();
}
const { run, destroy } = setup();
const { timing } = await fastest_test(10, () => {
for (let i = 0; i < 100; i++) {
run();
}
});
destroy();
return {
benchmark: 'kairo_unstable',
time: timing.time.toFixed(2),
gc_time: timing.gc_time.toFixed(2)
};
}

@ -0,0 +1,7 @@
export function busy() {
let a = 0;
for (let i = 0; i < 1_00; i++) {
a++;
}
}

@ -1,4 +1,4 @@
import { fastest_test } from '../utils.js'; import { assert, fastest_test } from '../utils.js';
import * as $ from '../../packages/svelte/src/internal/client/index.js'; import * as $ from '../../packages/svelte/src/internal/client/index.js';
/** /**
@ -23,10 +23,14 @@ function setup() {
const A = $.source(0); const A = $.source(0);
const B = $.source(0); const B = $.source(0);
const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2)); const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2));
const D = $.derived(() => numbers.map((i) => ({ x: i + ($.get(A) % 2) - ($.get(B) % 2) }))); const D = $.derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2)));
const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0].x)); D.equals = function (/** @type {number[]} */ l) {
const F = $.derived(() => hard($.get(D)[2].x || $.get(B))); var r = this.v;
const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[4].x + $.get(F)); return r !== null && l.length === r.length && l.every((v, i) => v === r[i]);
};
const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]));
const F = $.derived(() => hard($.get(D)[0] && $.get(B)));
const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0] + $.get(F));
const destroy = $.effect_root(() => { const destroy = $.effect_root(() => {
$.render_effect(() => { $.render_effect(() => {
@ -55,6 +59,7 @@ function setup() {
$.set(A, 2 + i * 2); $.set(A, 2 + i * 2);
$.set(B, 2); $.set(B, 2);
}); });
assert(res[0] === 3198 && res[1] === 1601 && res[2] === 3195 && res[3] === 1598);
} }
}; };
} }

@ -1,19 +1,48 @@
import * as $ from '../packages/svelte/src/internal/client/index.js'; import * as $ from '../packages/svelte/src/internal/client/index.js';
import { kairo_avoidable } from './benchmarks/kairo/kairo_avoidable.js';
import { kairo_broad } from './benchmarks/kairo/kairo_broad.js';
import { kairo_deep } from './benchmarks/kairo/kairo_deep.js';
import { kairo_diamond } from './benchmarks/kairo/kairo_diamond.js';
import { kairo_mux } from './benchmarks/kairo/kairo_mux.js';
import { kairo_repeated } from './benchmarks/kairo/kairo_repeated.js';
import { kairo_triangle } from './benchmarks/kairo/kairo_triangle.js';
import { kairo_unstable } from './benchmarks/kairo/kairo_unstable.js';
import { mol_bench } from './benchmarks/mol_bench.js'; import { mol_bench } from './benchmarks/mol_bench.js';
const benchmarks = [mol_bench]; // This benchmark has been adapted from the js-reactivity-benchmark (https://github.com/milomg/js-reactivity-benchmark)
// Not all tests are the same, and many parts have been tweaked to capture different data.
async function run_benchmarks() { const benchmarks = [
const results = []; kairo_avoidable,
kairo_broad,
kairo_deep,
kairo_diamond,
kairo_triangle,
kairo_mux,
kairo_repeated,
kairo_unstable,
mol_bench
];
async function run_benchmarks() {
// eslint-disable-next-line no-console
console.log('-- Benchmarking Started --');
$.push({}, true); $.push({}, true);
for (const benchmark of benchmarks) { try {
results.push(await benchmark()); for (const benchmark of benchmarks) {
// eslint-disable-next-line no-console
console.log(await benchmark());
}
} catch (e) {
// eslint-disable-next-line no-console
console.error('-- Benchmarking Failed --');
// eslint-disable-next-line no-console
console.error(e);
process.exit(1);
} }
$.pop(); $.pop();
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(results); console.log('-- Benchmarking Complete --');
} }
run_benchmarks(); run_benchmarks();

@ -20,8 +20,8 @@ class GarbageTrack {
} }
/** /**
* @param {number} track_id * @param {number} track_id
*/ */
async gcDuration(track_id) { async gcDuration(track_id) {
await promise_delay(10); await promise_delay(10);
@ -87,3 +87,12 @@ export async function fastest_test(times, fn) {
return fastest; return fastest;
} }
/**
* @param {boolean} a
*/
export function assert(a) {
if (!a) {
throw new Error('Assertion failed');
}
}

Loading…
Cancel
Save