Sma::eval is O(length) per cycle — rolling-sum O(1) is a precision trade-off #39
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Sma::evalrecomputes the window sum on every cycle — O(length) per eval (crates/aura-std/src/sma.rs). For large windows (e.g. SMA(200)) this is wasteful on the hot path, where the newEmais O(1).The textbook O(1) alternative is a rolling sum: keep a running total, add the newest value, subtract the value leaving the window.
The honest catch — this is a precision-vs-throughput trade-off, not a free win:
So a decision is needed, not just an optimization:
Filed as
idea(no commitment): the stable recompute is a defensible default for a determinism-first engine, so this is only worth doing if SMA-heavy sweeps show it on a profile. Surfaced while adding the O(1)Ema(commitae1d456), which made the contrast visible.Confirmed again in a fresh code-level performance audit — concrete fix recipe + a drift bound, in case it helps when this gets picked up.
Pattern to copy:
Ema(ema.rs:79-101) already does the O(1) recursive-stateversion this needs — same warm-up shape,
lookbacks() = vec![1].Incremental SMA: keep a running
sum: f64and the node's ownBox<[f64]>ring ofthe last
lengthsamples; per ticksum += x_new - x_evicted; out = sum / length.Then
lookbacks()drops tovec and thelength-sizedstorage moves into the node — net-same memory, O(1) compute. Warm-up identical to
today.
On the precision trade-off (the title's caveat): a running float sum drifts over
millions of ticks (cancellation on subtraction); the full re-sum does not. Both stay
deterministic (C1 holds), but the last ULPs change → new golden baseline, so per
CLAUDE.mdthis is a design decision (ledger entry), not a silent refactor.Mitigation that keeps it amortized O(1): re-sum from the ring every N ticks (e.g.
N=4096) to hard-bound the drift. The existing small-integer exact tests
(
sma_warms_up_then_tracks_the_window_meanetc.) stay bit-identical; only longfloat runs need a rebaselined fixture.
Impact is real for production windows — SMA(50/100/200) are standard and SMA-cross is
the repo's reference strategy; in a sweep it multiplies by the point count.