Skip to content

Optimize algorithm hot paths with typed-array CSR kernels - #34

Merged
davidkpiano merged 6 commits into
mainfrom
feature/jolly-maxwell-x84mfo
Aug 18, 2026
Merged

Optimize algorithm hot paths with typed-array CSR kernels#34
davidkpiano merged 6 commits into
mainfrom
feature/jolly-maxwell-x84mfo

Conversation

@davidkpiano

@davidkpiano davidkpiano commented Aug 17, 2026

Copy link
Copy Markdown
Member

Summary

Performance overhaul of the algorithm hot paths. Every rewrite preserves observable behavior — same results, same ordering, same laziness and error contracts — and all 1,852 existing tests pass unchanged.

  • getStronglyConnectedComponents — iterative typed-array Tarjan over the CSR out-arcs (single pass, stack-safe on deep graphs). ~15x faster.
  • getTopologicalSort — Kahn's algorithm over CSR arcs with a typed ring queue and per-CSR cached in-degrees, replacing the Map-based version whose queue.shift() was O(n²). ~20x faster.
  • isBipartite / getMaximumBipartiteMatching — 2-coloring runs directly over the cached CSR arcs instead of rebuilding an undirected adjacency on every call; the self-loop sweep now only runs on the success path. ~60x faster on repeated queries.
  • Bellman-Ford — typed relaxation over cached compact arc arrays (same relaxation order, so tie-predecessor enumeration is unchanged), plus a scalar-predecessor single-pair fast path for getShortestPath that returns the same path the full enumeration would yield first. ~15x faster.
  • Floyd-Warshall — flat Float64Array distance matrix with copy-on-write tie-predecessor pair lists, replacing nested object arrays with per-cell clones and some() dedup scans. ~4x faster.
  • genBFS / genDFS / genPostorder — hand-rolled Generator-protocol iterators (no generator resume machinery), with BFS/DFS batching work in 1024-node chunks served from a buffer. Yield order is unchanged (property-checked against the previous implementation across random/scale-free graphs and all directions), validation still throws on first next(), and mid-iteration mutation snapshots behave as before — now backed by the CSR's node snapshot instead of a per-iterator array copy. ~1.5–2x faster full traversals.
  • Dijkstra / A* / bidirectional search — default edge weights come from a per-arc Float64Array cached on the CSR, removing edge-object loads (and ?? 1 fallbacks) from the inner loops.
  • getDegree — served from a per-version degree map (one hashed lookup per call); a one-entry memo in front of the index WeakMap speeds all point-query bursts against the same graph.
  • All-targets shortest-path reconstruction — each path materializes once via a shared backtracking buffer, replacing per-recursion-level array spreads (O(L) instead of O(L²) per path). Also roughly halves the getAllPairsShortestPaths runtime on path-like graphs.

Notes for review

  • New caches (arc weights, edge-order arcs, in-degrees, degrees, nodes snapshot) all key off the existing GraphIndex/CSR version, so they inherit the documented staleness contract — mutation-API changes invalidate them, in-place field edits still require invalidateIndex().
  • Dangling-edge handling is normalized in a couple of spots (e.g. Floyd-Warshall previously crashed on edges with missing endpoints; they are now skipped like everywhere else).
  • The bipartite conflict edge named in getMaximumBipartiteMatching's error may differ from before when several edges prove non-bipartiteness (any witness is valid; tests only assert the message shape).

Testing

  • pnpm verify (typecheck, repo typecheck, generated schemas, conventions, full test suite, build, publint + package smoke test) passes.
  • Added-behavior equivalence was additionally property-checked for traversal ordering against the previous implementations.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe


Generated by Claude Code


Open in Devin Review

Rewrites the algorithm hot paths onto typed-array CSR kernels: iterative
Tarjan SCC, Kahn toposort with cached in-degrees, CSR-direct bipartite
coloring, compact-arc Bellman-Ford (plus a single-pair fast path),
flat-matrix Floyd-Warshall with copy-on-write tie lists, chunked
hand-rolled BFS/DFS/postorder iterators, cached per-arc default weights
for Dijkstra/A*/bidirectional search, a per-version degree map, and
O(length) shortest-path materialization via a shared backtracking buffer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
devin-ai-integration[bot]

This comment was marked as resolved.

claude added 2 commits August 17, 2026 11:38
BFS/DFS batches start at 8 nodes and double up to 1024, so taking the
first few nodes of a huge graph costs a few nodes of traversal (plus the
same O(n) visited/queue allocation the generator implementation paid),
while full traversals keep amortized chunked speed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
Floyd-Warshall no longer records tying self-loops as predecessors — the
sparse diagonal cell made the old push crash, and recording them would
let tie merging build self-referential lists that hang reconstruction.
Negative self-loops still surface via the negative-cycle check, and
reconstruction now carries the same on-path guard as the Dijkstra-side
enumeration. Adds regression tests for both shapes.

The one-entry index memo now holds WeakRefs, so it never extends the
lifetime of the most recently queried graph.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
devin-ai-integration[bot]

This comment was marked as resolved.

updateNode swaps the node object in place (same id/position, arrays
untouched), which no version or staleness check can observe — the CSR's
cached node snapshot kept serving the pre-update object to fresh
traversals. The index now notifies derived caches of node replacement
and the CSR patches the one slot in O(1), so traversals stay both fast
and current. Adds a regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
devin-ai-integration[bot]

This comment was marked as resolved.

The BFS/DFS fast serve paths check their cursors before the finished
flag, so an injected throw() left buffered nodes servable. return() and
throw() now share a close() hook that drops buffered output, matching
generator exhaustion semantics. Adds a regression test for both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
devin-ai-integration[bot]

This comment was marked as resolved.

Setup errors (e.g. radius validation) left the iterator half-initialized
with started=true; a second next() on the postorder iterator then hit
undefined internal state instead of returning done. Setup now runs
through a shared ensureStarted() that marks the iterator finished on
failure, matching generator semantics for all three traversals. Adds a
regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXF7Ba1P2JvvtCfiYbX9xe
@davidkpiano
davidkpiano merged commit 5a8eef6 into main Aug 18, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants