82 lines
2.6 KiB
Markdown
82 lines
2.6 KiB
Markdown
# 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 `Vec`s.
|
|
|
|
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
|
|
|
|
1. 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`.
|