Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Where the time goes

Two very different regimes show up in practice.

Compute-bound: Rips and GrPPH

For Vietoris–Rips and GrPPH the cost is in producing columns: enumerating cofacets, computing diameters or path lengths, comparing labels. The optimisations that matter are the ones that avoid building columns at all (apparent and emergent pairs, clearing, leaving zero-length pairs out of the basis) and cheap label arithmetic (Rips packs a simplex into one integer and computes its cofacets by inserting a digit; GrPPH compares paths through a packed 128-bit key).

Latency-bound: large explicit complexes

A 1000 × 1000 image (4 million cells) has surprisingly little work: about 2 million columns to look at, 94 thousand that need reducing, and about a million column additions. Its working set (matrices, pivot table, working column) is hundreds of megabytes, far beyond the CPU cache, and each step of the reduction is a chain of dependent random memory accesses: find the pivot, look up its owner, find the owner’s column, read its rows. Each miss costs on the order of 100 ns.

sequenceDiagram
    participant R as reduction
    participant T as pivot table
    participant M as matrix
    R->>T: owner of pivot r?
    T-->>R: column j (miss)
    R->>M: where is column j?
    M-->>R: offset (miss)
    R->>M: rows of column j
    M-->>R: rows (miss)
    Note over R: add them, find the next pivot, repeat

In this regime the number of misses per step is everything, and generality costs misses: a rank lookup, a filtration value lookup, a two-level map, recomputing an apparent owner through the transpose. Before it was tuned, phlite made four or five dependent misses per pivot lookup where PHAT makes one, and was 2× slower on such grids. Closing the gap meant letting explicit complexes declare PHAT-like structure through opt-in hooks:

ChangeGrid 1000 × 1000 (Rust)
starting point770 ms
renumber cells by rank (no change on its own: the misses moved elsewhere)779 ms
use the rank as the filtration value758 ms
inline maps, apparent pairs in the pivot table (full_basis)628 ms
identity row ranks (no table lookup)505 ms
stored R found from the pivot table463 ms
zero-length checks in order, basis in one pass442 ms
u32 column pointers435 ms

PHAT takes about 400 ms on the same matrix. What remains is mostly phlite doing more: it keeps the transpose too (for apparent pairs, cohomology and representatives), translates results back to your cells with values, and drops zero-length bars. On rings images and meshes it is now level with PHAT to within about 10%; on dense matrices and sparse cell complexes it is level or faster.

Measuring

phlite_py/examples/reduce_file.rs reduces a complex dumped from the benchmark cache, in pure Rust, so you can profile without Python:

RUSTFLAGS="-C force-frame-pointers=yes" cargo build --release -p phlite_py --example reduce_file
perf record -g target/release/examples/reduce_file grid1000.bin 5

perf stat -e cycles,instructions,LLC-load-misses quickly tells you which regime you are in.