Skip to content

Performance ​

Layouts run over graph snapshots: compact typed arrays with no object per node or edge. Results are typed arrays too, with dim numbers per node, so a layout of a million nodes allocates a few megabytes, not a million objects. What decides the running time is the layout you choose and how many iterations you give it.

Cost by Layout ​

V is the number of nodes, E the number of edges, and k the number of iterations.

LayoutTimeExtra memory
circular, random, grid, spiral, shellO(V)O(V)
bfs, radial, bipartite, multipartiteO(V + E)O(V)
planarO(V + E)O(V + E)
spectralO(k x (V + E)) per axisO(V)
forceAtlas2, fruchtermanReingold, arfO(k x V^2)O(V)
kamadaKawaiO(V^3) for the distances, then O(V^2) per solver stepO(V^2)

Measured Times ​

One run of each layout on one core of a desktop CPU (Intel i9-14900), with default options, gives a sense of scale. Your times will differ; the ratios between rows will not change much.

LayoutGraphTime
circular, random, grid, spiral, shell, bipartite200,000 nodesunder 20 ms
bfs200,000 nodes25 ms
radial200,000 nodes150 ms
planar90,000-node grid2 s
planar200,000-node tree5 s
spectral2,000-node random graph70 ms
spectral2,500-node grid0.5 s
spectral100,000-node scale-free graph100 s
fruchtermanReingold (50 iterations)1,000 nodes0.3 s
fruchtermanReingold (50 iterations)3,000 nodes3 s
forceAtlas2 (100 iterations)1,000 nodes0.6 s
forceAtlas2 (100 iterations)3,000 nodes3.7 s
forceAtlas2 (100 iterations)10,000 nodes37 s
kamadaKawai100 nodes1.2 s
kamadaKawai400 nodes16 s

In short: the geometric and structural layouts are instant at any size you can draw. The force-directed layouts compare every pair of nodes on every iteration, so their cost grows with the square of the node count; on the CPU they suit graphs of up to a few thousand nodes. spectral is fast on small and medium graphs and slows down sharply on very large ones and on long, thin graphs such as grids and paths. kamadaKawai is for small graphs only: beyond a few hundred nodes, use forceAtlas2 or fruchtermanReingold. It also keeps a matrix of every pair's distance, about 24 bytes per pair: 240 MB at 3,000 nodes.

Start From a Cheap Layout ​

A force-directed layout starting from random positions spends its first iterations untangling them. Starting from a cheap layout through pos gets to a good drawing in fewer iterations:

typescript
import { fromEdgeArrays } from "@graphty/graph-format";
import { erdosRenyiGraph } from "@graphty/graph-samples/generators";
import { fruchtermanReingold, spectral } from "@graphty/layout";

const graph = fromEdgeArrays(erdosRenyiGraph({ n: 2000, p: 0.002, seed: 3 }));
const start = spectral(graph, { seed: 3 });
const refined = fruchtermanReingold(graph, { pos: start.positions, iterations: 20 });
console.log(refined.n); // 2000

Animate Instead of Waiting ​

A steppable simulation shows a drawing after the first frame and improves it while the reader watches, instead of blocking until the last iteration. Stop as soon as settled is true, or after a time budget of your own: the position array always holds the latest drawing.

typescript
import { fromEdgeArrays } from "@graphty/graph-format";
import { barabasiAlbertGraph } from "@graphty/graph-samples/generators";
import { createSimulation } from "@graphty/layout";

const graph = fromEdgeArrays(barabasiAlbertGraph({ n: 500, m: 2, seed: 1 }));
const positions = new Float32Array(3 * graph.nodeCount).fill(NaN);
const sim = createSimulation("forceatlas2", { maxIter: 1000, seed: 1 });
sim.load(graph, positions);

const budget = performance.now() + 50; // at most 50 ms of layout
while (!sim.settled && performance.now() < budget) {
    sim.step(5);
}
sim.dispose();
console.log(Number.isFinite(positions[0])); // true

Kamada-Kawai on Large Graphs ​

Pass maxNodes so a graph too big for the distance matrix is refused before the allocation, with a RangeError whose code is E_TOO_LARGE, and choose another layout for it. See Force-Directed and Spectral.

Web Workers ​

A snapshot and a LayoutResult are typed arrays, so both move to and from a Web Worker without copying. Run a long layout in a worker to keep the page responsive: send the snapshot in, and post result.positions back with its buffer in the transfer list.

GPU Acceleration ​

For force-directed layouts of tens of thousands of nodes and more, the CPU is the bottleneck. @graphty/webgpu-graph-algorithms runs ForceAtlas2, Fruchterman-Reingold and a spring-electrical layout on the GPU with WebGPU, through the same createSimulation call (see GPU Acceleration). On small graphs the CPU is usually faster, because a GPU step has a fixed cost of its own.