Asymptotic complexity of each subsystem, and measured performance on real hardware. Full benchmark methodology and figures:
../scientific/benchmarks.mdand../scientific/methodology.md.
Let $m = \lvert x \rvert$ (query length), $n = \lvert y \rvert$ (target
length), $W$ = the learner window size, and $S$ = number of candidate
series.
| Operation | Time | Space |
|---|---|---|
perturb_c / explore_c |
$O(1)$ |
$O(1)$ |
predict(query, target) |
$O(mn)$ (one MSM distance) |
$O(\min(m,n))$ |
observe(cost) |
$O(W)$ (mean finite difference over the window) |
$O(W)$ window |
update_c |
$O(1)$ |
$O(1)$ |
The learner's own state is $O(W)$ (two bounded ring-like windows); with the
default $W = 10$ this is a handful of f64s. predict is dominated by the MSM
distance; observe is $O(W)$ and, with a small fixed $W$, effectively
$O(1)$ per round.
| Operation | Time | Space |
|---|---|---|
encode_msm_state / decode_msm_state |
$O(1)$ |
$O(1)$ |
| expand one state | $O(1)$ (at most 3 transitions) |
$O(1)$ |
fully expand one (query, target) lattice |
$O(mn)$ |
$O(mn)$ states |
| tropical shortest distance (unpruned) | $O(mn)$ |
$O(mn)$ (cached) or $O(\min(m,n))$ frontier |
The lattice has at most $(m{+}1)(n{+}1)$ states per series; with $S$
candidates the bound is $O(S,mn)$. max_cost pruning removes states off
the low-cost band and can reduce this dramatically (see the measured effect in §2
and E2). The cached engine bounds memory further via CachePolicy
(caching.md).
Hardware: AMD Ryzen Threadripper PRO 5975WX (32 cores, max 4.56 GHz), Linux
7.1.3 x86-64, performance governor, rustc 1.96.0, --release, single core.
Criterion medians (see docs/scientific/data/bench.txt):
$n$ (both series) |
median time | ratio vs. $n/4$ |
|---|---|---|
| 16 | 1.03 µs | — |
| 64 | 17.66 µs | 17.1× |
| 256 | 291.4 µs | 16.5× |
| 1024 | 4.576 ms | 15.7× |
Each $4\times$ length increase multiplies time by $\approx 16 = 4^2$ — the
quadratic signature, exactly as predicted.
$n$ |
median time | ratio vs. $n/2$ |
|---|---|---|
| 16 | 65.9 µs | — |
| 32 | 263.8 µs | 4.0× |
| 64 | 1.088 ms | 4.1× |
A $2\times$ length increase multiplies time by $\approx 4 = 2^2$ — the WFST
materialises $\Theta(n^2)$ states, each in $O(1)$. The constant is larger
than the raw DP (hashing, registry, weight tracking, caching), so use the WFST when
you need composition; use MsmConfig::distance when you only need the number.
One explore_c + observe round: 55.8 ns — negligible relative to any real
scoring, so online adaptation is essentially free on top of whatever produces the
cost signal.
max_cost caps the materialised state count independently of $n$ once the band
is exceeded — e.g. at $n = 64$ the unpruned lattice has 4225 states, but
max_cost = 16 materialises 80 and max_cost = 4 only 7 (data:
docs/scientific/data/state_scaling.csv, figure
../scientific/figures/state_scaling.svg).
- Preallocation. Windows and registries are constructed with capacity
(
Vec::with_capacity,FxHashMap::with_capacity_and_hasher) where the size is known — a workspace standard, not a premature optimization. FxHashMap(rustc-hash) for the registry/cache: fast non-cryptographic hashing suited to integerStateIdkeys.SmallVecfor transition lists: at most 3–4 transitions per state fit inline, avoiding heap allocation on the expansion hot path.- Two-row DP upstream:
MsmConfig::distanceuses$O(\min(m,n))$space.