2.6 KiB
Feature: Reduce Per-Step Allocation
Overview
Remove the heap allocations every accepted step performs.
Why This Feature Matters
Each call to step() allocates a Vec for the stage values
(vec![SVector::zeros(); STAGES]) and a second Vec for the dense output
coefficients. Problem::solve then wraps the latter in a RefCell and pushes
it onto a growing vector. That is two allocations per step against roughly
150 ns of arithmetic.
Measured 2026-09-29 on the harmonic oscillator at 1e-10, where the right-hand side is two flops so the timing is almost entirely integrator overhead:
| Method | Steps | Total | Per step |
|---|---|---|---|
| BS3 | 13,927 | 1.58 ms | 113 ns |
| DP45 | 722 | 112 us | 155 ns |
| Vern7 | 124 | 26.6 us | 215 ns |
BS3::step in isolation is 44.6 ns for four stages.
The gain is largest exactly where it is least important: problems with cheap right-hand sides. For the orbit propagation this crate is aimed at, a spherical-harmonic gravity evaluation dominates and per-step overhead is rounding error. Worth doing, not worth doing first.
Dependencies
None. STAGES is already an associated const, so the stage array can be a
fixed-size array rather than a Vec.
Implementation Approach
(To be detailed.) Sketch: stage storage becomes [SVector<T, D>; STAGES] if
const generics allow it at the trait level, or a caller-supplied scratch buffer
threaded through step(). Dense coefficients want a pre-reserved buffer sized
from an estimate of the step count, or a flat Vec<SVector<T, D>> indexed by
stride rather than a Vec of Vecs.
Note the interaction with the RefCell in Solution::dense: it exists so
Vern7 can append its lazily computed extra stages. A flat buffer has to keep
that possible.
Implementation Tasks
- Core implementation
- Integration with existing code
- Testing
- Benchmarking
Testing Requirements
Numerics must not change. The existing benches plus a step-count and final-state comparison against the previous release are sufficient: results should be bit-identical.
References
- Julia implementation: OrdinaryDiffEq.jl, which caches stage storage in the integrator rather than allocating per step
Complexity Estimate
Effort: Medium
Risk: Low - no change to the numerics, so the test suite is a strong guard
Success Criteria
- No allocation in the steady-state step loop
- Bit-identical results against the previous release
- Measurable improvement on the cheap-RHS benchmarks
Future Enhancements
Supersedes the "Optimize memory allocation in step method" bullet under Future
Enhancements in 01-bs3-method.md.