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
4 changes: 4 additions & 0 deletions Cargo.lock

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

34 changes: 34 additions & 0 deletions js/json/bench/codec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/** Sweep snapshot codec cost over document size and changed keys. */
import { Decoder } from "../src/snapshot/decoder.ts";
import { Encoder } from "../src/snapshot/encoder.ts";

const updates = 256;
console.log("keys\tchanged\tcompression\tms/update");
for (const keys of [1, 10, 100, 1000]) {
for (const changed of new Set([1, Math.min(10, keys)])) {
for (const compression of ["none", "deflate"] as const) {
const samples: number[] = [];
for (let sample = 0; sample < 3; sample++) {
const config = { deltaRatio: 100, compression };
const encoder = new Encoder<Record<string, number>>(config);
const decoder = new Decoder<Record<string, number>>(config);
let value = Object.fromEntries(Array.from({ length: keys }, (_, n) => [`k${n}`, 0]));
const start = performance.now();
for (let n = 1; n <= updates; n++) {
value = { ...value };
for (let key = 0; key < changed; key++) value[`k${key}`] = n;
const frame = encoder.update(value);
if (frame) {
if (frame.keyframe) decoder.snapshot(frame.payload);
else decoder.delta(frame.payload);
frame.commit();
}
if (decoder.decode()?.k0 !== n) throw new Error("codec benchmark lost an update");
}
samples.push((performance.now() - start) / updates);
}
samples.sort((a, b) => a - b);
console.log(`${keys}\t${changed}\t${compression}\t${samples[1]?.toFixed(6)}`);
}
}
}
5 changes: 5 additions & 0 deletions js/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ bench name:
bun install --frozen-lockfile
bun net/bench/{{ name }}.ts

# Time Effect create and close, which pays for the creation stack its diagnostics print.
signals-bench:
bun install --frozen-lockfile
bun signals/bench/effect.ts

# Build all packages.
build:
bun run --filter='*' build
Expand Down
3 changes: 3 additions & 0 deletions js/net/bench/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import * as Path from "../src/path.ts";
import { Reader, Stream } from "../src/stream.ts";
import * as Varint from "../src/varint.ts";

// Keep protocol diagnostics out of the measured CSV on stdout.
console.debug = console.error;

const frameSizes = [16, 100, 1000];
const chunkSizes = [1200, 16 * 1024, 64 * 1024];
const framesPerGroup = 50;
Expand Down
59 changes: 59 additions & 0 deletions js/signals/bench/effect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** Time creating and closing an Effect, bare and with a tracked signal, against a bare `new Error()`. */
import { Effect, Signal } from "../src/index.ts";

const runs = 20_000;
const reps = 9;
const signal = new Signal(0);
let checksum = 0;
let tracked = 0;

const ops: [string, () => void | Promise<void>][] = [
// The creation stack each Effect captures for its diagnostics; the floor for the rows below.
[
"new-error",
() => {
checksum += new Error("created here:").message.length;
},
],
[
"create-close",
() => {
new Effect().close();
checksum++;
},
],
[
"create-get-close",
async () => {
const effect = new Effect((effect) => {
tracked += effect.get(signal) + 1;
});
// The callback runs in a microtask, so yield once to let it track the signal before closing.
await Promise.resolve();
effect.close();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run the tracked effect before closing it

Effect schedules its callback in a microtask, but this closes the instance synchronously, so every scheduled run observes the closed state and returns without executing effect.get(signal). Consequently, the create-get-close row measures scheduling and immediate closure rather than creating and closing an effect with a tracked signal, while the queued callbacks drain outside the timed section and the checksum from the other rows masks the omission.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid: the callback never ran, so the row timed scheduling plus an immediate close. Fixed in 465fb4a: the row yields once so the effect tracks the signal before close(), the timing loop awaits only rows that return a promise (the sync rows pay no extra hop), and a separate tracked counter fails the run if the effect never executes. main's js/signals/bench/effect.ts has the same bug and needs the same fix for the before/after rows to pair.

(Written by Claude Opus 5.5)

},
],
];

// Nanoseconds per op for the fastest of several reps, since a slower one measured the machine.
async function time(body: () => void | Promise<void>): Promise<number> {
let best = Number.POSITIVE_INFINITY;
for (let rep = 0; rep < reps; rep++) {
const start = performance.now();
for (let i = 0; i < runs; i++) {
// Awaiting a sync row would add a microtask hop it doesn't pay.
const pending = body();
if (pending instanceof Promise) await pending;
}
best = Math.min(best, ((performance.now() - start) * 1e6) / runs);
}
return best;
}

// Time every op once first and discard it, so the JIT has seen all of them and the first row isn't timing warmup.
for (const [, body] of ops) await time(body);

console.log("op,ns_per_op");
for (const [op, body] of ops) console.log(`${op},${(await time(body)).toFixed(0)}`);
if (checksum === 0) throw new Error("benchmark did no work");
if (tracked === 0) throw new Error("create-get-close never ran its effect");
4 changes: 4 additions & 0 deletions rs/kio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,9 @@ harness = false
name = "channel"
harness = false

[[bench]]
name = "contended"
harness = false

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }
Loading
Loading