Skip to content
Open
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
67 changes: 8 additions & 59 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concurrency:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read # clone repo
steps:
Expand All @@ -32,17 +32,7 @@ jobs:
with:
deno-version: v2.x

- name: Cache WASM
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: wasm-cache
with:
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
Expand All @@ -63,52 +53,7 @@ jobs:
retention-days: 1
path: wasm.ts

simulation:
name: Run benchmarks (simulation)
needs: build
runs-on: codspeed-macro
permissions:
contents: read # clone repo
id-token: write # upload benchmark results to codspeed

steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Deno
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: v2.x

- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24

- name: Download build artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: bench-build

- name: Restore dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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.17.0
with:
mode: simulation
# IMPORTANT! deno task bench fails in CI due to incompatible V8 bindings
run: node bench/mod.ts

walltime:
benchmarks:
name: Run benchmarks (walltime)
needs: build
runs-on: codspeed-macro
Expand Down Expand Up @@ -146,8 +91,12 @@ jobs:
if: steps.deno-cache.outputs.cache-hit != 'true'
run: deno install

- name: Run process startup benchmarks
- name: Run walltime benchmarks
uses: CodSpeedHQ/action@9d332c4d90b43981c3e55ae8e38e68709996240f # v4.17.0
with:
mode: walltime
run: node bench/startup.bench.ts
run: |
node bench/startup.bench.ts
node bench/throughput.bench.ts
node bench/render.bench.ts
node bench/ops.bench.ts
55 changes: 0 additions & 55 deletions bench/input.bench.ts

This file was deleted.

2 changes: 1 addition & 1 deletion bench/mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import "./input.bench.ts";
import "./throughput.bench.ts";
import "./render.bench.ts";
import "./ops.bench.ts";
32 changes: 9 additions & 23 deletions bench/ops.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@ import { withCodSpeed } from "@codspeed/tinybench-plugin";
import { close, fixed, grow, open, pack, rgba, text } from "../ops.ts";
import type { Op } from "../ops.ts";

function makeBuf(size: number): ArrayBuffer {
return new ArrayBuffer(size);
}

let simpleOps: Op[] = [
open("root", {
layout: { width: grow(), height: grow(), direction: "ttb" },
}),
text("Hello, World!"),
close(),
];

let complexOps: Op[] = [
open("root", {
layout: { width: grow(), height: grow(), direction: "ttb" },
Expand Down Expand Up @@ -104,20 +92,18 @@ let listOps: Op[] = [
close(),
];

let bench = withCodSpeed(new Bench());
let buf = new ArrayBuffer(32768);

let bench = withCodSpeed(new Bench({ name: "ops" }));

bench
.add("simple tree (root + text)", () => {
let buf = makeBuf(4096);
pack(simpleOps, buf, 0);
})
.add("complex layout (header + sidebar + main + footer)", () => {
let buf = makeBuf(8192);
pack(complexOps, buf, 0);
.add("pack complex layout", () => {
for (let i = 0; i < 1500; i++) pack(complexOps, buf, 0);
return Promise.resolve();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we add a comment or perhaps a README.md in this folder explaining why this is required? I am assuming it is to force a last microtask or something? I don't see anything within the tinybench API that would point to the value of doing this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I'll add that! It's a limitation of the @codspeed/plugin-tinybench package, which only expects the task to be async. With synchronous tasks, it throws an error like "cannot destructure 'latency' from undefined"

})
.add("large list (50 items)", () => {
let buf = makeBuf(32768);
pack(listOps, buf, 0);
.add("pack large list", () => {
for (let i = 0; i < 250; i++) pack(listOps, buf, 0);
return Promise.resolve();
});

await bench.run();
Expand Down
60 changes: 11 additions & 49 deletions bench/render.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,6 @@ import { close, fixed, grow, open, rgba, text } from "../ops.ts";
import type { Op } from "../ops.ts";

let term = await createTerm({ width: 80, height: 24 });
let termPtr = await createTerm({ width: 80, height: 24 });

let helloOps: Op[] = [
open("root", {
layout: { width: grow(), height: grow(), direction: "ttb" },
}),
text("Hello, World!"),
close(),
];

let borderedOps: Op[] = [
open("root", {
layout: { width: grow(), height: grow(), direction: "ttb" },
}),
open("box", {
layout: {
width: grow(),
height: grow(),
padding: { left: 1, right: 1, top: 1, bottom: 1 },
direction: "ttb",
},
border: {
color: rgba(0, 255, 0),
left: 1,
right: 1,
top: 1,
bottom: 1,
},
cornerRadius: { tl: 1, tr: 1, bl: 1, br: 1 },
}),
text("Bordered content"),
close(),
close(),
];

let dashboardOps: Op[] = [
open("root", {
Expand Down Expand Up @@ -134,24 +100,20 @@ let uiOps: Op[] = [
close(),
];

let bench = withCodSpeed(new Bench());
let bench = withCodSpeed(new Bench({ name: "render" }));

bench
.add("simple text", () => {
term.render(helloOps);
})
.add("bordered box with corner radius", () => {
term.render(borderedOps);
})
.add("dashboard layout", () => {
term.render(dashboardOps);
})
.add("diff render (second frame)", () => {
term.render(dashboardOps);
term.render(dashboardOps);
.add("render mixed frames", () => {
for (let i = 0; i < 250; i++) {
term.render(i % 2 === 0 ? dashboardOps : uiOps);
}
return Promise.resolve();
})
.add("render with pointer hit testing", () => {
termPtr.render(uiOps, { pointer: { x: 10, y: 1, down: false } });
.add("render steady diff", () => {
for (let i = 0; i < 250; i++) {
term.render(dashboardOps);
}
return Promise.resolve();
});

await bench.run();
Expand Down
47 changes: 47 additions & 0 deletions bench/throughput.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Bench } from "tinybench";
import { withCodSpeed } from "@codspeed/tinybench-plugin";
import { createInput } from "../input.ts";

function str(s: string): Uint8Array {
return new TextEncoder().encode(s);
}

function concat(parts: Uint8Array[]): Uint8Array {
let len = parts.reduce((n, p) => n + p.length, 0);
let out = new Uint8Array(len);
let off = 0;
for (let p of parts) {
out.set(p, off);
off += p.length;
}
return out;
}

let unit = concat([
str("the quick brown fox "),
new Uint8Array([0x1b, 0x5b, 0x41]), // ArrowUp
str("\x1b[<0;40;12M"), // SGR mouse press
new Uint8Array([0xe4, 0xb8, 0xad]), // 中
str("\x1b[97;3u"), // Kitty a+Alt
new Uint8Array([0xf0, 0x9f, 0x8e, 0x89]), // 🎉
]);
let corpus = concat(new Array(1000).fill(unit));

let READ = 64;

let input = await createInput({ escLatency: 25 });

let bench = withCodSpeed(new Bench({ name: "throughput" }));

bench.add("input throughput (mixed corpus, chunked read loop)", () => {
let dispatched = 0;
for (let off = 0; off < corpus.length; off += READ) {
let { events } = input.scan(corpus.subarray(off, off + READ));
dispatched += events.length;
}
if (dispatched === 0) throw new Error("expected events");
return Promise.resolve();
});

await bench.run();
console.table(bench.table());
Loading
Loading