Benchmarks
Our viewer against cosmos.gl alone on the same positions, how far cosmos.gl's live layout goes, and where the layout's tuning was measured.
The harness is a showcase you can run. It has two layers, and they are not variants of each other:
GraphRoot is the path a product uses — a graph attached with attachGraph and drawn whole by
<GraphRoot from engine><GraphCanvas/></GraphRoot> — and cosmos.gl alone is the control, the same renderer
fed typed arrays with nothing of ours in the way.
Build the graphs first
The viewer layer reads GraphAr graphs written by fossil's writer, @fossil-lang/executor, which
runs bench.fossil in Node once, offline; the browser only reads them.
node docs/showcases/graph-bench/corpus/build-corpus.mjsThey land in docs/public/bench/<size>/ and are gitignored, fossil.graph.yml, mapping.ttl and the Parquet chunks. The million is not written: the executor panics in DataFusion's memory pool inside
its WASM at that size, so the script fails there and the viewer's million is a 404 until fossil
fixes it.
Our viewer against cosmos.gl alone
measureViewer(pointCount), registered on window by the showcase, attaches a built graph, reads
its links on the page's engine and draws it twice — with cosmos.gl alone and with
<GraphRoot simulate={false}><GraphCanvas/></GraphRoot> — over the same seeded start, alternating
the two three times. Each run follows one
camera path: the same wheel events on each canvas, a change every frame, zooming into a quarter of
the extent and then in and out at a point that sweeps across it. It records the interval between
frames, and counts the requestAnimationFrame calls made during a second of nothing, which is zero
for a loop that idles.
await measureViewer(200000); // in the showcase's console, in a foreground windowThe gate is the viewer's p95 frame interval within 15% of cosmos.gl's, the median over the three runs.
| Nodes | cosmos.gl alone, p95 | GraphRoot, p95 |
|---|---|---|
| 200,000 | 9.3 ms | 9.0 ms |
The viewer was inside the gate on 2026-09-30, when it read the graph's own laid-out positions; the harness has moved to the seeded start since and the row has not been re-run on it.
The engine: how far a live layout goes
The engine layer generates a graph in the tab and runs cosmos.gl's simulation over it. Per step is
the mean of batched graph.step() calls flushed by a getPointPositions() readback, so it is real
GPU work, and Step ceiling is what that cost implies on its own. Frames counts cosmos.gl's own
onSimulationTick over a wall-clock window.
Read Step ceiling, not Frames, to judge whether a layout keeps up. WebGL commands queue
without the CPU waiting, so the loop keeps presenting frames at vsync while the GPU falls behind —
the picture is smooth and stale at once. Only the readback in Per step forces the queue to drain.
| Nodes | Links | Generate | Upload | Per step | Step ceiling | Frames |
|---|---|---|---|---|---|---|
| 2k | 12.5k | 5 ms | 60 ms | 1.52 ms | 656 fps | 120 fps |
| 10k | 69.3k | 15 ms | 79 ms | 4.35 ms | 230 fps | 120 fps |
| 50k | 331.6k | 65 ms | 192 ms | 10.03 ms | 100 fps | 80 fps |
| 200k | 1.4M | 454 ms | 751 ms | 61.00 ms | 16 fps | 61 fps |
This is cosmos.gl with links drawn and its decay untuned: comfortable to about 50,000 points and stalling by 200,000. The graph's own layout goes further by cooling sooner, widening its square and hiding links while it runs — Layout and camera has those rules and the gate that measured them, at up to 500,000 vertices.
Discovery workspace
A recipe — the engine, a graph attached by name, and the graph parts and charts on one crossfilter.
AI
What knows a model is on the other end — Tasks, where a small model fills an artefact that oracles check; a field the model helps fill; a conversation, with data or without — over the AI SDK, reached through @kanzo-tech/llm, a model in the page. A sibling of @kanzo-tech/ui, never part of it.