Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
"name": "js-reactivity-benchmark",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"tsc": "tsc",
"test": "vitest",
"build": "esbuild src/index.ts --bundle --format=iife --target=esnext --outfile=public/dist/index.js --sourcemap=inline",
"prepare": "esbuild src/index.ts --bundle --format=esm --target=esnext --outdir=dist --sourcemap=external && tsc -p tsconfig.d.json",
"build": "esbuild src/main.ts --bundle --format=iife --target=esnext --outfile=public/dist/index.js --sourcemap=inline",
"start": "serve public",
"chrome": "chrome --no-sandbox --js-flags=--log-deopt,--log-ic,--log-maps,--log-maps-details,--log-internal-timer-events,--prof,--expose-gc,--detailed-line-info --enable-precise-memory-info --user-data-dir=$(PWD)/chrome http://localhost:3000"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"devDependencies": {
"@angular/core": "19.1.5",
"@preact/signals": "^2.0.1",
"@reactively/core": "^0.0.8",
Expand All @@ -29,9 +32,7 @@
"signal-polyfill": "^0.2.2",
"solid-js": "^1.9.4",
"svelte": "^5.20.0",
"usignal": "^0.9.0"
},
"devDependencies": {
"usignal": "^0.9.0",
"@types/node": "^22.13.1",
"esbuild": "^0.25.0",
"serve": "^14.2.4",
Expand Down
27 changes: 13 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/benches/cellxBench.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The following is an implementation of the cellx benchmark https://github.com/Riim/cellx/blob/master/perf/perf.html
import { nextTick } from "../util/asyncUtil";
import { logPerfResult } from "../util/perfLogging";
import { PerfResultCallback } from "../util/perfLogging";
import { Computed, ReactiveFramework } from "../util/reactiveFramework";

const cellx = (framework: ReactiveFramework, layers: number) => {
Expand Down Expand Up @@ -88,7 +88,7 @@ type BenchmarkResults = [
readonly [number, number, number, number],
];

export const cellxbench = async (framework: ReactiveFramework) => {
export const cellxbench = async (framework: ReactiveFramework, logPerfResult: PerfResultCallback) => {
const expected: Record<number, BenchmarkResults> = {
1000: [
[-3, -6, -2, 2],
Expand Down Expand Up @@ -121,7 +121,7 @@ export const cellxbench = async (framework: ReactiveFramework) => {
logPerfResult({
framework: framework.name,
test: `cellx${layers}`,
time: total.toFixed(2),
time: total,
});
}

Expand Down
6 changes: 3 additions & 3 deletions src/benches/kairoBench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { triangle } from "./kairo/triangle";
import { unstable } from "./kairo/unstable";
import { nextTick } from "../util/asyncUtil";
import { fastestTest } from "../util/benchRepeat";
import { logPerfResult } from "../util/perfLogging";
import { ReactiveFramework } from "../util/reactiveFramework";
import { PerfResultCallback } from "../util/perfLogging";

const cases = [
avoidablePropagation,
Expand All @@ -22,7 +22,7 @@ const cases = [
unstable,
];

export async function kairoBench(framework: ReactiveFramework) {
export async function kairoBench(framework: ReactiveFramework, logPerfResult: PerfResultCallback) {
for (const c of cases) {
const iter = framework.withBuild(() => {
const iter = c(framework);
Expand All @@ -44,7 +44,7 @@ export async function kairoBench(framework: ReactiveFramework) {
logPerfResult({
framework: framework.name,
test: c.name,
time: timing.time.toFixed(2),
time: timing.time,
});
}
}
6 changes: 3 additions & 3 deletions src/benches/molBench.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { nextTick } from "../util/asyncUtil";
import { fastestTest } from "../util/benchRepeat";
import { logPerfResult } from "../util/perfLogging";
import { PerfResultCallback } from "../util/perfLogging";
import { ReactiveFramework } from "../util/reactiveFramework";

function fib(n: number): number {
Expand All @@ -14,7 +14,7 @@ function hard(n: number, _log: string) {

const numbers = Array.from({ length: 5 }, (_, i) => i);

export async function molBench(framework: ReactiveFramework) {
export async function molBench(framework: ReactiveFramework, logPerfResult: PerfResultCallback) {
let res = [];
const iter = framework.withBuild(() => {
const A = framework.signal(0);
Expand Down Expand Up @@ -65,6 +65,6 @@ export async function molBench(framework: ReactiveFramework) {
logPerfResult({
framework: framework.name,
test: "molBench",
time: timing.time.toFixed(2),
time: timing.time,
});
}
25 changes: 21 additions & 4 deletions src/benches/reactively/dynamicBench.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { makeGraph, runGraph } from "./dependencyGraph";
import { logPerfResult, perfRowStrings } from "../../util/perfLogging";
import { verifyBenchResult } from "../../util/perfTests";
import { FrameworkInfo } from "../../util/frameworkTypes";
import { FrameworkInfo, TestConfig } from "../../util/frameworkTypes";
import { perfTests } from "../../config";
import { fastestTest } from "../../util/benchRepeat";
import { PerfResultCallback } from "../../util/perfLogging";

function percent(n: number): string {
return Math.round(n * 100) + "%";
}

export function makeTitle(config: TestConfig): string {
const { width, totalLayers, staticFraction, nSources, readFraction } = config;
const dyn = staticFraction < 1 ? " - dynamic" : "";
const read = readFraction < 1 ? ` - read ${percent(readFraction)}` : "";
const sources = ` - ${nSources} sources`;
return `${width}x${totalLayers}${sources}${dyn}${read}`;
}

/** benchmark a single test under single framework.
* The test is run multiple times and the fastest result is logged to the console.
*/
export async function dynamicBench(
frameworkTest: FrameworkInfo,
testRepeats = 5,
logPerfResult: PerfResultCallback,
testRepeats = 5
): Promise<void> {
const { framework } = frameworkTest;
for (const config of perfTests) {
Expand All @@ -33,7 +46,11 @@ export async function dynamicBench(
return { sum, count: counter.count };
});

logPerfResult(perfRowStrings(framework.name, config, timedResult));
logPerfResult({
framework: framework.name,
test: `${makeTitle(config)} (${config.name || ""})`,
time: timedResult.timing.time,
});
verifyBenchResult(frameworkTest, config, timedResult);
}
}
12 changes: 6 additions & 6 deletions src/benches/sBench.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Inspired by https://github.com/solidjs/solid/blob/main/packages/solid/bench/bench.cjs

import { logPerfResult } from "../util/perfLogging";
import { PerfResultCallback } from "../util/perfLogging";
import { Computed, ReactiveFramework, Signal } from "../util/reactiveFramework";


const COUNT = 1e5;

type Reader = () => number;
export function sbench(framework: ReactiveFramework) {
export function sbench(framework: ReactiveFramework, logPerfResult: PerfResultCallback) {
bench(createDataSignals, COUNT, COUNT);
bench(createComputations0to1, COUNT, 0);
bench(createComputations1to1, COUNT, COUNT);
Expand Down Expand Up @@ -36,7 +36,7 @@ export function sbench(framework: ReactiveFramework) {
logPerfResult({
framework: framework.name,
test: fn.name,
time: time.toFixed(2),
time: time,
});
}

Expand All @@ -50,7 +50,7 @@ export function sbench(framework: ReactiveFramework) {
let end = 0;

framework.withBuild(() => {
if (window.gc) gc!(), gc!();
if (globalThis.gc) gc!(), gc!();

// run 3 times to warm up
let sources: Computed<number>[] | null = createDataSignals(scount, []);
Expand All @@ -67,15 +67,15 @@ export function sbench(framework: ReactiveFramework) {
}

// start GC clean
if (window.gc) gc!(), gc!();
if (globalThis.gc) gc!(), gc!();

start = performance.now();

fn(n, sources);

// end GC clean
sources = null;
if (window.gc) gc!(), gc!();
if (globalThis.gc) gc!(), gc!();
end = performance.now();
});

Expand Down
39 changes: 1 addition & 38 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,4 @@
import { TestConfig, FrameworkInfo } from "./util/frameworkTypes";

import { angularFramework as angularFramework2 } from "./frameworks/angularSignals2";
// import { compostateFramework } from "./frameworks/inactive/compostate";
// import { kairoFramework } from "./frameworks/inactive/kairo";
// import { mobxFramework } from "./frameworks/inactive/mobx";
import { molWireFramework } from "./frameworks/molWire";
// import { obyFramework } from "./frameworks/inactive/oby";
import { preactSignalFramework } from "./frameworks/preactSignals";
import { reactivelyFramework } from "./frameworks/reactively";
// import { sFramework } from "./frameworks/inactive/s";
import { solidFramework } from "./frameworks/solid";
// import { usignalFramework } from "./frameworks/inactive/uSignal";
// import { vueReactivityFramework } from "./frameworks/inactive/vueReactivity";
// import { xReactivityFramework } from "./frameworks/inactive/xReactivity";
import { alienFramework } from "./frameworks/alienSignals";
import { svelteFramework } from "./frameworks/svelte";
// import { tc39SignalsFramework } from "./frameworks/tc39signals";

export const frameworkInfo: FrameworkInfo[] = [
{ framework: alienFramework, testPullCounts: true },
{ framework: angularFramework2, testPullCounts: true },
// { framework: angularFramework, testPullCounts: true },
// { framework: compostateFramework, testPullCounts: true },
// { framework: kairoFramework, testPullCounts: true },
// { framework: mobxFramework, testPullCounts: true },
{ framework: molWireFramework, testPullCounts: true },
// { framework: obyFramework, testPullCounts: true },
{ framework: preactSignalFramework, testPullCounts: true },
{ framework: reactivelyFramework, testPullCounts: true },
// { framework: sFramework },
{ framework: solidFramework }, // solid can't testPullCounts because batch executes all leaf nodes even if unread
{ framework: svelteFramework, testPullCounts: true },
// { framework: tc39SignalsFramework, testPullCounts: true },
// { framework: usignalFramework, testPullCounts: true },
// { framework: vueReactivityFramework, testPullCounts: true },
// { framework: xReactivityFramework, testPullCounts: true },
];
import { TestConfig } from "./util/frameworkTypes";

export const perfTests: TestConfig[] = [
{
Expand Down
2 changes: 1 addition & 1 deletion src/frameworks.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { makeGraph, runGraph } from "./benches/reactively/dependencyGraph";
import { expect, test } from "vitest";
import { FrameworkInfo, TestConfig } from "./util/frameworkTypes";
import { frameworkInfo } from "./config";
import { frameworkInfo } from "./frameworksList";

frameworkInfo.forEach((frameworkInfo) => frameworkTests(frameworkInfo));

Expand Down
37 changes: 37 additions & 0 deletions src/frameworksList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FrameworkInfo } from "./util/frameworkTypes";
import { angularFramework as angularFramework2 } from "./frameworks/angularSignals2";
// import { compostateFramework } from "./frameworks/inactive/compostate";
// import { kairoFramework } from "./frameworks/inactive/kairo";
// import { mobxFramework } from "./frameworks/inactive/mobx";
import { molWireFramework } from "./frameworks/molWire";
// import { obyFramework } from "./frameworks/inactive/oby";
import { preactSignalFramework } from "./frameworks/preactSignals";
import { reactivelyFramework } from "./frameworks/reactively";
// import { sFramework } from "./frameworks/inactive/s";
import { solidFramework } from "./frameworks/solid";
// import { usignalFramework } from "./frameworks/inactive/uSignal";
// import { vueReactivityFramework } from "./frameworks/inactive/vueReactivity";
// import { xReactivityFramework } from "./frameworks/inactive/xReactivity";
import { alienFramework } from "./frameworks/alienSignals";
import { svelteFramework } from "./frameworks/svelte";
// import { tc39SignalsFramework } from "./frameworks/tc39signals";

export const frameworkInfo: FrameworkInfo[] = [
{ framework: alienFramework, testPullCounts: true },
{ framework: angularFramework2, testPullCounts: true },
// { framework: angularFramework, testPullCounts: true },
// { framework: compostateFramework, testPullCounts: true },
// { framework: kairoFramework, testPullCounts: true },
// { framework: mobxFramework, testPullCounts: true },
{ framework: molWireFramework, testPullCounts: true },
// { framework: obyFramework, testPullCounts: true },
{ framework: preactSignalFramework, testPullCounts: true },
{ framework: reactivelyFramework, testPullCounts: true },
// { framework: sFramework },
{ framework: solidFramework }, // solid can't testPullCounts because batch executes all leaf nodes even if unread
{ framework: svelteFramework, testPullCounts: true },
// { framework: tc39SignalsFramework, testPullCounts: true },
// { framework: usignalFramework, testPullCounts: true },
// { framework: vueReactivityFramework, testPullCounts: true },
// { framework: xReactivityFramework, testPullCounts: true },
];
Loading