From d66845a4284b3c69983bc3d4838f1930abbaf8f6 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 29 May 2026 00:31:31 -0400 Subject: [PATCH 01/16] bench: add WASM init and startup timing benchmarks Adds bench/startup.bench.ts with 6 cases covering cold compile, instantiation, createTermNative, createInputNative, createTerm, and full module-to-first-render startup time. Also exports `bytes` from wasm.ts so the compile step can be benchmarked independently. Co-Authored-By: Claude Sonnet 4.6 --- bench/mod.ts | 1 + bench/startup.bench.ts | 51 ++++++++++++++++++++++++++++++++++++++++++ tasks/bundle-wasm.ts | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 bench/startup.bench.ts diff --git a/bench/mod.ts b/bench/mod.ts index ca9de34..cf98ae3 100644 --- a/bench/mod.ts +++ b/bench/mod.ts @@ -1,3 +1,4 @@ import "./input.bench.ts"; import "./render.bench.ts"; import "./ops.bench.ts"; +import "./startup.bench.ts"; diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts new file mode 100644 index 0000000..8f532c6 --- /dev/null +++ b/bench/startup.bench.ts @@ -0,0 +1,51 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { bytes, compiled } from "../wasm.ts"; +import { createTermNative } from "../term-native.ts"; +import { createInputNative } from "../input-native.ts"; +import { createTerm } from "../term.ts"; +import { close, grow, open, text } from "../ops.ts"; +import type { Op } from "../ops.ts"; + +const helloOps: Op[] = [ + open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), + text("Hello, World!"), + close(), +]; + +let bench = withCodSpeed(new Bench()); + +bench + .add("wasm compile", async () => { + await WebAssembly.compile(bytes); + }) + .add("wasm instantiate", async () => { + const memory = new WebAssembly.Memory({ initial: 2 }); + await WebAssembly.instantiate(compiled, { + env: { memory }, + clay: { + measureTextFunction() {}, + queryScrollOffsetFunction(ret: number) { + const v = new DataView(memory.buffer); + v.setFloat32(ret, 0, true); + v.setFloat32(ret + 4, 0, true); + }, + }, + }); + }) + .add("createTermNative (80x24)", async () => { + await createTermNative(80, 24); + }) + .add("createInputNative", async () => { + await createInputNative(50); + }) + .add("createTerm (80x24)", async () => { + await createTerm({ width: 80, height: 24 }); + }) + .add("startup: createTerm + first render (80x24)", async () => { + const term = await createTerm({ width: 80, height: 24 }); + term.render(helloOps); + }); + +await bench.run(); +console.table(bench.table()); diff --git a/tasks/bundle-wasm.ts b/tasks/bundle-wasm.ts index b6b033d..662c108 100644 --- a/tasks/bundle-wasm.ts +++ b/tasks/bundle-wasm.ts @@ -4,7 +4,7 @@ const wasm = await Deno.readFile("clayterm.wasm"); const base64 = encodeBase64(wasm); const source = `const bin = atob("${base64}"); -const bytes = new Uint8Array(bin.length); +export const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); export const compiled = await WebAssembly.compile(bytes); `; From c9bb21a686634d79d834006e479e3e213a021c88 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 29 May 2026 00:39:10 -0400 Subject: [PATCH 02/16] deno lint --- bench/startup.bench.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts index 8f532c6..ad08e6b 100644 --- a/bench/startup.bench.ts +++ b/bench/startup.bench.ts @@ -20,13 +20,13 @@ bench await WebAssembly.compile(bytes); }) .add("wasm instantiate", async () => { - const memory = new WebAssembly.Memory({ initial: 2 }); + let memory = new WebAssembly.Memory({ initial: 2 }); await WebAssembly.instantiate(compiled, { env: { memory }, clay: { measureTextFunction() {}, queryScrollOffsetFunction(ret: number) { - const v = new DataView(memory.buffer); + let v = new DataView(memory.buffer); v.setFloat32(ret, 0, true); v.setFloat32(ret + 4, 0, true); }, @@ -43,7 +43,7 @@ bench await createTerm({ width: 80, height: 24 }); }) .add("startup: createTerm + first render (80x24)", async () => { - const term = await createTerm({ width: 80, height: 24 }); + let term = await createTerm({ width: 80, height: 24 }); term.render(helloOps); }); From 6eb4995be2c2c943b569151e39209ee91082a4c5 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 29 May 2026 20:19:33 -0400 Subject: [PATCH 03/16] split startup benchmark --- .github/workflows/benchmark.yml | 4 +++- bench/mod.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 123abef..09e752e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -45,4 +45,6 @@ jobs: with: mode: simulation # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings - run: node bench/mod.ts + run: | + node bench/mod.ts + node bench/startup.bench.ts diff --git a/bench/mod.ts b/bench/mod.ts index cf98ae3..ca9de34 100644 --- a/bench/mod.ts +++ b/bench/mod.ts @@ -1,4 +1,3 @@ import "./input.bench.ts"; import "./render.bench.ts"; import "./ops.bench.ts"; -import "./startup.bench.ts"; From cd1448a82fa2374c39e5f262736aafcc6e9db7bd Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 00:18:32 -0500 Subject: [PATCH 04/16] ref(bench): split startup cases --- .github/workflows/benchmark.yml | 3 +- bench/startup.bench.ts | 51 ------------------------------- bench/startup/createTerm.bench.ts | 14 +++++++++ bench/startup/ttfr.bench.ts | 20 ++++++++++++ 4 files changed, 36 insertions(+), 52 deletions(-) delete mode 100644 bench/startup.bench.ts create mode 100644 bench/startup/createTerm.bench.ts create mode 100644 bench/startup/ttfr.bench.ts diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 09e752e..4cbc977 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -47,4 +47,5 @@ jobs: # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings run: | node bench/mod.ts - node bench/startup.bench.ts + node bench/startup/createTerm.bench.ts + node bench/startup/ttfr.bench.ts diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts deleted file mode 100644 index ad08e6b..0000000 --- a/bench/startup.bench.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Bench } from "tinybench"; -import { withCodSpeed } from "@codspeed/tinybench-plugin"; -import { bytes, compiled } from "../wasm.ts"; -import { createTermNative } from "../term-native.ts"; -import { createInputNative } from "../input-native.ts"; -import { createTerm } from "../term.ts"; -import { close, grow, open, text } from "../ops.ts"; -import type { Op } from "../ops.ts"; - -const helloOps: Op[] = [ - open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), - text("Hello, World!"), - close(), -]; - -let bench = withCodSpeed(new Bench()); - -bench - .add("wasm compile", async () => { - await WebAssembly.compile(bytes); - }) - .add("wasm instantiate", async () => { - let memory = new WebAssembly.Memory({ initial: 2 }); - await WebAssembly.instantiate(compiled, { - env: { memory }, - clay: { - measureTextFunction() {}, - queryScrollOffsetFunction(ret: number) { - let v = new DataView(memory.buffer); - v.setFloat32(ret, 0, true); - v.setFloat32(ret + 4, 0, true); - }, - }, - }); - }) - .add("createTermNative (80x24)", async () => { - await createTermNative(80, 24); - }) - .add("createInputNative", async () => { - await createInputNative(50); - }) - .add("createTerm (80x24)", async () => { - await createTerm({ width: 80, height: 24 }); - }) - .add("startup: createTerm + first render (80x24)", async () => { - let term = await createTerm({ width: 80, height: 24 }); - term.render(helloOps); - }); - -await bench.run(); -console.table(bench.table()); diff --git a/bench/startup/createTerm.bench.ts b/bench/startup/createTerm.bench.ts new file mode 100644 index 0000000..ea431c6 --- /dev/null +++ b/bench/startup/createTerm.bench.ts @@ -0,0 +1,14 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { createTerm } from "../../term.ts"; + +let bench = withCodSpeed(new Bench({ name: 'startup', })); + +bench + .add("createTerm", async () => { + await createTerm({ width: 80, height: 24 }); + }); + +await bench.run(); +console.table(bench.table()); + diff --git a/bench/startup/ttfr.bench.ts b/bench/startup/ttfr.bench.ts new file mode 100644 index 0000000..2c5abb9 --- /dev/null +++ b/bench/startup/ttfr.bench.ts @@ -0,0 +1,20 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { createTerm } from "../../term.ts"; +import { close, grow, open, text } from "../../ops.ts"; + +let bench = withCodSpeed(new Bench({ name: 'startup' })); + +bench + .add("time to first render", async () => { + let term = await createTerm({ width: 80, height: 24 }); + term.render([ + open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), + text("Hello, World!"), + close(), + ]); + }); + +await bench.run(); +console.table(bench.table()); + From 67ad1a1fe7ec03f0ff52782a6f2ded42d4da123b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 04:35:22 -0500 Subject: [PATCH 05/16] ref(bench): spawn fixture --- .github/workflows/benchmark.yml | 40 +++++++++++++++++++++++++--- bench/fixtures/create-term/mod.ts | 3 +++ bench/fixtures/render-minimal/mod.ts | 9 +++++++ bench/fixtures/utils.ts | 9 +++++++ bench/startup.bench.ts | 12 +++++++++ bench/startup/createTerm.bench.ts | 14 ---------- bench/startup/ttfr.bench.ts | 20 -------------- 7 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 bench/fixtures/create-term/mod.ts create mode 100644 bench/fixtures/render-minimal/mod.ts create mode 100644 bench/fixtures/utils.ts create mode 100644 bench/startup.bench.ts delete mode 100644 bench/startup/createTerm.bench.ts delete mode 100644 bench/startup/ttfr.bench.ts diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 4cbc977..996fd3c 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -45,7 +45,39 @@ jobs: with: mode: simulation # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings - run: | - node bench/mod.ts - node bench/startup/createTerm.bench.ts - node bench/startup/ttfr.bench.ts + run: node bench/mod.ts + + startup-process-benchmarks: + name: Run process startup benchmarks + runs-on: codspeed-macro + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Build WASM + run: make + + - name: Install dependencies + run: deno install + + - name: Run process startup benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: walltime + run: node bench/startup.bench.ts diff --git a/bench/fixtures/create-term/mod.ts b/bench/fixtures/create-term/mod.ts new file mode 100644 index 0000000..b391f21 --- /dev/null +++ b/bench/fixtures/create-term/mod.ts @@ -0,0 +1,3 @@ +import { createTerm } from "../../../term.ts"; + +await createTerm({ width: 80, height: 24 }); diff --git a/bench/fixtures/render-minimal/mod.ts b/bench/fixtures/render-minimal/mod.ts new file mode 100644 index 0000000..6430785 --- /dev/null +++ b/bench/fixtures/render-minimal/mod.ts @@ -0,0 +1,9 @@ +import { createTerm } from "../../../term.ts"; +import { close, grow, open, text } from "../../../ops.ts"; + +let term = await createTerm({ width: 80, height: 24 }); +term.render([ + open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), + text("Hello, World!"), + close(), +]); diff --git a/bench/fixtures/utils.ts b/bench/fixtures/utils.ts new file mode 100644 index 0000000..85014b6 --- /dev/null +++ b/bench/fixtures/utils.ts @@ -0,0 +1,9 @@ +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +export const fixture = (name: string) => { + return new URL(`./${name}/mod.ts`, import.meta.url); +} + +export const spawnFixture = (name: string) => + spawnSync(process.execPath, [...process.execArgv, fileURLToPath(fixture(name))], { stdio: "ignore" }); diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts new file mode 100644 index 0000000..679b8e1 --- /dev/null +++ b/bench/startup.bench.ts @@ -0,0 +1,12 @@ +import { Bench } from "tinybench"; +import { withCodSpeed } from "@codspeed/tinybench-plugin"; +import { spawnFixture } from './fixtures/utils.ts'; + +let bench = withCodSpeed(new Bench({ name: "startup" })); + +bench + .add("createTerm", () => spawnFixture('create-term')) + .add("time to first render", () => spawnFixture('render-minimal')); + +await bench.run(); +console.table(bench.table()); diff --git a/bench/startup/createTerm.bench.ts b/bench/startup/createTerm.bench.ts deleted file mode 100644 index ea431c6..0000000 --- a/bench/startup/createTerm.bench.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Bench } from "tinybench"; -import { withCodSpeed } from "@codspeed/tinybench-plugin"; -import { createTerm } from "../../term.ts"; - -let bench = withCodSpeed(new Bench({ name: 'startup', })); - -bench - .add("createTerm", async () => { - await createTerm({ width: 80, height: 24 }); - }); - -await bench.run(); -console.table(bench.table()); - diff --git a/bench/startup/ttfr.bench.ts b/bench/startup/ttfr.bench.ts deleted file mode 100644 index 2c5abb9..0000000 --- a/bench/startup/ttfr.bench.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Bench } from "tinybench"; -import { withCodSpeed } from "@codspeed/tinybench-plugin"; -import { createTerm } from "../../term.ts"; -import { close, grow, open, text } from "../../ops.ts"; - -let bench = withCodSpeed(new Bench({ name: 'startup' })); - -bench - .add("time to first render", async () => { - let term = await createTerm({ width: 80, height: 24 }); - term.render([ - open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), - text("Hello, World!"), - close(), - ]); - }); - -await bench.run(); -console.table(bench.table()); - From ca688b7cc8eae2fda3051d914a69cb601c785ef4 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 04:40:39 -0500 Subject: [PATCH 06/16] deno fmt --- bench/fixtures/render-minimal/mod.ts | 6 +++--- bench/fixtures/utils.ts | 9 ++++++--- bench/startup.bench.ts | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bench/fixtures/render-minimal/mod.ts b/bench/fixtures/render-minimal/mod.ts index 6430785..13b52bb 100644 --- a/bench/fixtures/render-minimal/mod.ts +++ b/bench/fixtures/render-minimal/mod.ts @@ -3,7 +3,7 @@ import { close, grow, open, text } from "../../../ops.ts"; let term = await createTerm({ width: 80, height: 24 }); term.render([ - open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), - text("Hello, World!"), - close(), + open("root", { layout: { width: grow(), height: grow(), direction: "ttb" } }), + text("Hello, World!"), + close(), ]); diff --git a/bench/fixtures/utils.ts b/bench/fixtures/utils.ts index 85014b6..6b9c2e5 100644 --- a/bench/fixtures/utils.ts +++ b/bench/fixtures/utils.ts @@ -2,8 +2,11 @@ import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; export const fixture = (name: string) => { - return new URL(`./${name}/mod.ts`, import.meta.url); -} + return new URL(`./${name}/mod.ts`, import.meta.url); +}; export const spawnFixture = (name: string) => - spawnSync(process.execPath, [...process.execArgv, fileURLToPath(fixture(name))], { stdio: "ignore" }); + spawnSync(process.execPath, [ + ...process.execArgv, + fileURLToPath(fixture(name)), + ], { stdio: "ignore" }); diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts index 679b8e1..5a559d0 100644 --- a/bench/startup.bench.ts +++ b/bench/startup.bench.ts @@ -1,12 +1,12 @@ import { Bench } from "tinybench"; import { withCodSpeed } from "@codspeed/tinybench-plugin"; -import { spawnFixture } from './fixtures/utils.ts'; +import { spawnFixture } from "./fixtures/utils.ts"; let bench = withCodSpeed(new Bench({ name: "startup" })); bench - .add("createTerm", () => spawnFixture('create-term')) - .add("time to first render", () => spawnFixture('render-minimal')); + .add("createTerm", () => spawnFixture("create-term")) + .add("time to first render", () => spawnFixture("render-minimal")); await bench.run(); console.table(bench.table()); From 258f70a3e2e7f4c4d00c2c14ab09c99e043bf085 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 04:54:36 -0500 Subject: [PATCH 07/16] ref(bench): fix return --- bench/startup.bench.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts index 5a559d0..5597cbb 100644 --- a/bench/startup.bench.ts +++ b/bench/startup.bench.ts @@ -5,8 +5,12 @@ import { spawnFixture } from "./fixtures/utils.ts"; let bench = withCodSpeed(new Bench({ name: "startup" })); bench - .add("createTerm", () => spawnFixture("create-term")) - .add("time to first render", () => spawnFixture("render-minimal")); + .add("createTerm", () => { + spawnFixture("create-term"); + }) + .add("time to first render", () => { + spawnFixture("render-minimal"); + }); await bench.run(); console.table(bench.table()); From 3f6c395e1513c076ce31e147ec5b760d115e2d2c Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:07:58 -0500 Subject: [PATCH 08/16] ref(bench): async fix --- bench/startup.bench.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts index 5597cbb..1535ce0 100644 --- a/bench/startup.bench.ts +++ b/bench/startup.bench.ts @@ -5,10 +5,10 @@ import { spawnFixture } from "./fixtures/utils.ts"; let bench = withCodSpeed(new Bench({ name: "startup" })); bench - .add("createTerm", () => { + .add("createTerm", async () => { spawnFixture("create-term"); }) - .add("time to first render", () => { + .add("time to first render", async () => { spawnFixture("render-minimal"); }); From bad790bd84e122c7becedcdf7c5993f3ab41d148 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:14:29 -0500 Subject: [PATCH 09/16] ref(bench): return promise --- bench/fixtures/utils.ts | 14 ++++++++------ bench/startup.bench.ts | 8 ++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bench/fixtures/utils.ts b/bench/fixtures/utils.ts index 6b9c2e5..68bf77d 100644 --- a/bench/fixtures/utils.ts +++ b/bench/fixtures/utils.ts @@ -1,12 +1,14 @@ import { fileURLToPath } from "node:url"; -import { spawnSync } from "node:child_process"; +import { spawn } from "node:child_process"; export const fixture = (name: string) => { return new URL(`./${name}/mod.ts`, import.meta.url); }; -export const spawnFixture = (name: string) => - spawnSync(process.execPath, [ - ...process.execArgv, - fileURLToPath(fixture(name)), - ], { stdio: "ignore" }); +export const spawnFixture = (name: string): Promise => + new Promise((resolve) => { + spawn(process.execPath, [ + ...process.execArgv, + fileURLToPath(fixture(name)), + ], { stdio: "ignore" }).on("close", resolve); + }); diff --git a/bench/startup.bench.ts b/bench/startup.bench.ts index 1535ce0..5a559d0 100644 --- a/bench/startup.bench.ts +++ b/bench/startup.bench.ts @@ -5,12 +5,8 @@ import { spawnFixture } from "./fixtures/utils.ts"; let bench = withCodSpeed(new Bench({ name: "startup" })); bench - .add("createTerm", async () => { - spawnFixture("create-term"); - }) - .add("time to first render", async () => { - spawnFixture("render-minimal"); - }); + .add("createTerm", () => spawnFixture("create-term")) + .add("time to first render", () => spawnFixture("render-minimal")); await bench.run(); console.table(bench.table()); From 4c5211b05faee7231c5ee593220a9ba61983b82b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:15:07 -0500 Subject: [PATCH 10/16] ref(ci): update job names --- .github/workflows/benchmark.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 996fd3c..2208d13 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -14,8 +14,8 @@ permissions: id-token: write jobs: - benchmarks: - name: Run benchmarks + simulation: + name: Run benchmarks (simulation) runs-on: ubuntu-latest steps: @@ -47,8 +47,8 @@ jobs: # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings run: node bench/mod.ts - startup-process-benchmarks: - name: Run process startup benchmarks + walltime: + name: Run benchmarks (walltime) runs-on: codspeed-macro permissions: contents: read From 282c442a49fa27de4680c91c279fabef8a221044 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:30:46 -0500 Subject: [PATCH 11/16] ref(bench): share artifacts between jobs --- .github/workflows/benchmark.yml | 79 ++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 2208d13..f9aa987 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,11 +11,14 @@ on: permissions: contents: read - id-token: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - simulation: - name: Run benchmarks (simulation) + build: + name: Build runs-on: ubuntu-latest steps: @@ -29,26 +32,70 @@ jobs: with: deno-version: v2.x - - name: Setup Node - uses: actions/setup-node@v4 + - name: Cache WASM + uses: actions/cache@v4 + id: wasm-cache with: - node-version: 22 + path: | + clayterm.wasm + wasm.ts + key: wasm-${{ hashFiles('Makefile', 'src/**', 'tasks/bundle-wasm.ts') }} - name: Build WASM + if: steps.wasm-cache.outputs.cache-hit != 'true' run: make + - name: Cache dependencies + uses: actions/cache@v4 + id: deno-cache + with: + path: node_modules + key: deno-${{ hashFiles('deno.lock') }} + - name: Install dependencies + if: steps.deno-cache.outputs.cache-hit != 'true' run: deno install + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: bench-build + retention-days: 1 + path: | + wasm.ts + node_modules + + simulation: + name: Run benchmarks (simulation) + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: bench-build + - name: Run benchmarks uses: CodSpeedHQ/action@v4 with: mode: simulation - # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings run: node bench/mod.ts walltime: name: Run benchmarks (walltime) + needs: build runs-on: codspeed-macro permissions: contents: read @@ -57,24 +104,16 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - with: - submodules: true - - - name: Setup Deno - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 - - name: Build WASM - run: make - - - name: Install dependencies - run: deno install + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: bench-build - name: Run process startup benchmarks uses: CodSpeedHQ/action@v4 From 81972761c45ac7b26c18e39780367e1a7a6dbb1d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:32:01 -0500 Subject: [PATCH 12/16] ref(bench): pin shas --- .github/workflows/benchmark.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f9aa987..c4b04a3 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -23,17 +23,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: submodules: true - name: Setup Deno - uses: denoland/setup-deno@v2 + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2 with: deno-version: v2.x - name: Cache WASM - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 id: wasm-cache with: path: | @@ -46,7 +46,7 @@ jobs: run: make - name: Cache dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 id: deno-cache with: path: node_modules @@ -57,7 +57,7 @@ jobs: run: deno install - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: bench-build retention-days: 1 @@ -75,20 +75,20 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24 - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: bench-build - name: Run benchmarks - uses: CodSpeedHQ/action@v4 + uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4 with: mode: simulation run: node bench/mod.ts @@ -103,20 +103,20 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24 - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: bench-build - name: Run process startup benchmarks - uses: CodSpeedHQ/action@v4 + uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4 with: mode: walltime run: node bench/startup.bench.ts From bb9d607e6acb7daef5c6df346069573f50ea923b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:35:13 -0500 Subject: [PATCH 13/16] ref(ci): add @codspeed/core directly --- deno.json | 1 + deno.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/deno.json b/deno.json index 901feb9..d4c8f82 100644 --- a/deno.json +++ b/deno.json @@ -17,6 +17,7 @@ "effection": "npm:effection@^4.0.2", "@std/encoding": "jsr:@std/encoding@1", "@codspeed/tinybench-plugin": "npm:@codspeed/tinybench-plugin@^5.4.0", + "@codspeed/core": "npm:@codspeed/core@^5.4.0", "tinybench": "npm:tinybench@^5.0.0" }, "exports": { diff --git a/deno.lock b/deno.lock index d0f8ef2..c68198e 100644 --- a/deno.lock +++ b/deno.lock @@ -20,6 +20,7 @@ "jsr:@std/testing@1": "1.0.17", "jsr:@ts-morph/bootstrap@0.27": "0.27.0", "jsr:@ts-morph/common@0.27": "0.27.0", + "npm:@codspeed/core@^5.4.0": "5.4.0", "npm:@codspeed/tinybench-plugin@^5.4.0": "5.4.0_tinybench@5.1.0", "npm:@sinclair/typebox@*": "0.34.48", "npm:@sinclair/typebox@0.34": "0.34.48", @@ -335,6 +336,7 @@ "jsr:@std/encoding@1", "jsr:@std/expect@1", "jsr:@std/testing@1", + "npm:@codspeed/core@^5.4.0", "npm:@codspeed/tinybench-plugin@^5.4.0", "npm:@sinclair/typebox@0.34", "npm:effection@^4.0.2", From 678797005bf64619ba267cfa11aaac404cf9db4d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 05:42:17 -0500 Subject: [PATCH 14/16] ref(ci): deno install in each job --- .github/workflows/benchmark.yml | 37 ++++++++++++++++++++++++++++++--- deno.json | 1 - 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c4b04a3..de5281e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -61,9 +61,7 @@ jobs: with: name: bench-build retention-days: 1 - path: | - wasm.ts - node_modules + path: wasm.ts simulation: name: Run benchmarks (simulation) @@ -77,6 +75,11 @@ jobs: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Setup Deno + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2 + with: + deno-version: v2.x + - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -87,10 +90,22 @@ jobs: with: name: bench-build + - name: Restore dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: deno-cache + with: + path: node_modules + key: deno-${{ hashFiles('deno.lock') }} + + - name: Install dependencies + if: steps.deno-cache.outputs.cache-hit != 'true' + run: deno install + - name: Run benchmarks uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4 with: mode: simulation + # IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings run: node bench/mod.ts walltime: @@ -105,6 +120,11 @@ jobs: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Setup Deno + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2 + with: + deno-version: v2.x + - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: @@ -115,6 +135,17 @@ jobs: with: name: bench-build + - name: Restore dependencies + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + id: deno-cache + with: + path: node_modules + key: deno-codspeed-${{ runner.arch }}-${{ hashFiles('deno.lock') }} + + - name: Install dependencies + if: steps.deno-cache.outputs.cache-hit != 'true' + run: deno install + - name: Run process startup benchmarks uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4 with: diff --git a/deno.json b/deno.json index d4c8f82..901feb9 100644 --- a/deno.json +++ b/deno.json @@ -17,7 +17,6 @@ "effection": "npm:effection@^4.0.2", "@std/encoding": "jsr:@std/encoding@1", "@codspeed/tinybench-plugin": "npm:@codspeed/tinybench-plugin@^5.4.0", - "@codspeed/core": "npm:@codspeed/core@^5.4.0", "tinybench": "npm:tinybench@^5.0.0" }, "exports": { From 2166f6e3ab60c00e020fd247fde0443703f06681 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 07:09:58 -0500 Subject: [PATCH 15/16] ref(deps): update lockfile --- deno.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/deno.lock b/deno.lock index c68198e..d0f8ef2 100644 --- a/deno.lock +++ b/deno.lock @@ -20,7 +20,6 @@ "jsr:@std/testing@1": "1.0.17", "jsr:@ts-morph/bootstrap@0.27": "0.27.0", "jsr:@ts-morph/common@0.27": "0.27.0", - "npm:@codspeed/core@^5.4.0": "5.4.0", "npm:@codspeed/tinybench-plugin@^5.4.0": "5.4.0_tinybench@5.1.0", "npm:@sinclair/typebox@*": "0.34.48", "npm:@sinclair/typebox@0.34": "0.34.48", @@ -336,7 +335,6 @@ "jsr:@std/encoding@1", "jsr:@std/expect@1", "jsr:@std/testing@1", - "npm:@codspeed/core@^5.4.0", "npm:@codspeed/tinybench-plugin@^5.4.0", "npm:@sinclair/typebox@0.34", "npm:effection@^4.0.2", From a44157b3184ee299f00385859178053cc3979b7b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 30 May 2026 07:10:16 -0500 Subject: [PATCH 16/16] ref(build): do not export bytes --- tasks/bundle-wasm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/bundle-wasm.ts b/tasks/bundle-wasm.ts index 662c108..b6b033d 100644 --- a/tasks/bundle-wasm.ts +++ b/tasks/bundle-wasm.ts @@ -4,7 +4,7 @@ const wasm = await Deno.readFile("clayterm.wasm"); const base64 = encodeBase64(wasm); const source = `const bin = atob("${base64}"); -export const bytes = new Uint8Array(bin.length); +const bytes = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); export const compiled = await WebAssembly.compile(bytes); `;