Skip to content

Commit

Permalink
Benchmarks from js-reactivity-benchmark in CI
Browse files Browse the repository at this point in the history
  • Loading branch information
divdavem committed Nov 12, 2024
1 parent 4061983 commit fa0cef5
Show file tree
Hide file tree
Showing 20 changed files with 1,758 additions and 303 deletions.
19 changes: 17 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ concurrency:
group: ci-${{ github.head_ref || github.ref }}
cancel-in-progress: true

permissions:
contents: write

jobs:
test:
name: 'Test ${{ matrix.testenv.name }}'
Expand All @@ -19,8 +22,8 @@ jobs:
matrix:
testenv:
- {name: 'Node', args: ''}
- {name: 'Chrome', args: '--browser.name=chrome --browser.headless'}
- {name: 'Firefox', args: '--browser.name=firefox --browser.headless'}
- {name: 'Chrome', args: '--browser.provider=webdriverio --browser.name=chrome --browser.headless'}
- {name: 'Firefox', args: '--browser.provider=webdriverio --browser.name=firefox --browser.headless'}

steps:
- uses: actions/checkout@v4
Expand All @@ -29,3 +32,15 @@ jobs:
- run: pnpm lint
- run: pnpm build
- run: pnpm vitest ${{ matrix.testenv.args }}
- run: pnpm vitest bench
if: matrix.testenv.name == 'Node'
- uses: benchmark-action/github-action-benchmark@d48d326b4ca9ba73ca0cd0d59f108f9e02a381c7 #v1.20.4
if: matrix.testenv.name == 'Node'
with:
name: Benchmarks
tool: 'customBiggerIsBetter'
output-file-path: benchmarks.json
auto-push: ${{ github.event_name == 'push' }}
github-token: ${{ secrets.GITHUB_TOKEN }}
comment-on-alert: true
summary-always: true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ build
dist/
*.tsbuildinfo
.DS_Store

# Benchmarks result:
benchmarks.json
33 changes: 33 additions & 0 deletions benchmarks/effect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Signal} from '../src';

const queue: Signal.Computed<any>[] = [];

export const effect = (fn: () => void) => {
const c = new Signal.Computed(fn);
const watcher = new Signal.subtle.Watcher(() => {
if (inBatch === 0) {
throw new Error('signal changed outside of batch');
}
queue.push(c);
watcher.watch(); // re-enable watcher
});
c.get();
watcher.watch(c);
};

const processQueue = () => {
while (queue.length) {
queue.shift()!.get();
}
};

let inBatch = 0;
export const batch = (fn: () => void) => {
inBatch++;
try {
fn();
} finally {
inBatch--;
if (inBatch === 0) processQueue();
}
};
6 changes: 6 additions & 0 deletions benchmarks/gc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const gc = globalThis.gc;
export const setup = gc
? () => {
gc();
}
: () => {};
94 changes: 94 additions & 0 deletions benchmarks/js-reactivity-benchmarks/cellxBench.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// adapted from https://github.com/milomg/js-reactivity-benchmark/blob/main/src/cellxBench.ts

import {bench, expect} from 'vitest';
import {Signal} from '../../src';
import {setup} from '../gc';
import {effect, batch} from '../effect';

// The following is an implementation of the cellx benchmark https://github.com/Riim/cellx/blob/master/perf/perf.html

const cellx = (
layers: number,
expectedBefore: readonly [number, number, number, number],
expectedAfter: readonly [number, number, number, number],
) => {
const start = {
prop1: new Signal.State(1),
prop2: new Signal.State(2),
prop3: new Signal.State(3),
prop4: new Signal.State(4),
};

let layer: {
prop1: Signal.Computed<number> | Signal.State<number>;
prop2: Signal.Computed<number> | Signal.State<number>;
prop3: Signal.Computed<number> | Signal.State<number>;
prop4: Signal.Computed<number> | Signal.State<number>;
} = start;

for (let i = layers; i > 0; i--) {
const m = layer;
const s = {
prop1: new Signal.Computed(() => m.prop2.get()),
prop2: new Signal.Computed(() => m.prop1.get() - m.prop3.get()),
prop3: new Signal.Computed(() => m.prop2.get() + m.prop4.get()),
prop4: new Signal.Computed(() => m.prop3.get()),
};

effect(() => s.prop1.get());
effect(() => s.prop2.get());
effect(() => s.prop3.get());
effect(() => s.prop4.get());

s.prop1.get();
s.prop2.get();
s.prop3.get();
s.prop4.get();

layer = s;
}

const end = layer;

expect(end.prop1.get()).toBe(expectedBefore[0]);
expect(end.prop2.get()).toBe(expectedBefore[1]);
expect(end.prop3.get()).toBe(expectedBefore[2]);
expect(end.prop4.get()).toBe(expectedBefore[3]);

batch(() => {
start.prop1.set(4);
start.prop2.set(3);
start.prop3.set(2);
start.prop4.set(1);
});

expect(end.prop1.get()).toBe(expectedAfter[0]);
expect(end.prop2.get()).toBe(expectedAfter[1]);
expect(end.prop3.get()).toBe(expectedAfter[2]);
expect(end.prop4.get()).toBe(expectedAfter[3]);
};

type BenchmarkResults = [
readonly [number, number, number, number],
readonly [number, number, number, number],
];

const expected: Record<number, BenchmarkResults> = {
1000: [
[-3, -6, -2, 2],
[-2, -4, 2, 3],
],
2500: [
[-3, -6, -2, 2],
[-2, -4, 2, 3],
],
5000: [
[2, 4, -1, -6],
[-2, 1, -4, -4],
],
};

for (const layers in expected) {
const params = expected[layers];
bench(`cellx${layers}`, () => cellx(+layers, params[0], params[1]), {throws: true, setup});
}
Loading

0 comments on commit fa0cef5

Please sign in to comment.