Kanzo UI
Graph

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.mjs

They 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 window

The gate is the viewer's p95 frame interval within 15% of cosmos.gl's, the median over the three runs.

Nodescosmos.gl alone, p95GraphRoot, p95
200,0009.3 ms9.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.

NodesLinksGenerateUploadPer stepStep ceilingFrames
2k12.5k5 ms60 ms1.52 ms656 fps120 fps
10k69.3k15 ms79 ms4.35 ms230 fps120 fps
50k331.6k65 ms192 ms10.03 ms100 fps80 fps
200k1.4M454 ms751 ms61.00 ms16 fps61 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.

On this page