Outcome: SmallVec optimization for State positions is NOT VIABLE ❌
After testing SmallVec sizes 4, 8, and 12, none provided universal performance improvements. The fundamental issue is that State size varies dramatically based on workload, making any fixed stack allocation size problematic.
From Phase 3 profiling, State cloning was identified as the next bottleneck (21.73% of runtime). The initial hypothesis was that using SmallVec<[Position; N]> instead of Vec<Position> would reduce allocation overhead during State cloning.
Tested three SmallVec sizes for State.positions:
Best Performance:
Worst Regressions:
Best Performance:
Worst Regressions:
Almost Universal Regressions:
Only One Improvement:
State size varies dramatically based on query characteristics:
Small States (Few Positions):
Large States (Many Positions):
Size 4: Minimal stack pressure, but frequent heap allocations for larger states
Size 8: Moderate stack pressure, covers more states
Size 12: Excessive stack pressure
The issue is workload variability:
SmallVec's fixed size means we're always wrong for some portion of the workload.
Phase 2 encountered similar issues with SmallVec<[Position; 8]> in transducer operations:
The lesson: SmallVec works when size is predictable; fails when size varies widely
PROFILING_COMPARISON.md suggested:
"Option C: SmallVec for Positions
- Impact: Reduces allocation overhead (6.00%)
- Complexity: Low - just change type
- Risk: May have stack overhead (Phase 2 lesson!)"
The "Risk" warning was correct! We should have weighted the Phase 2 lessons more heavily.
Concept:
fn transition_state_mut(state: &mut State, ...) {
epsilon_closure_mut(state, ...);
// Reuse state's Vec allocation
}
Pros:
Cons:
Verdict: Worth investigating if State cloning remains a bottleneck
Concept:
pub struct PathMapNode {
map: Arc<RwLock<PathMap<()>>>,
path: Arc<Vec<u8>>, // Share paths, cheap clone
}
Target: 5.14% overhead from path cloning
Pros:
Cons:
Verdict: Lower priority but simpler win
Concept:
fn transition_state(state: Cow<State>, ...) -> Cow<State> {
// Clone only when necessary
}
Pros:
Cons:
Verdict: Not recommended
When Phase 2 showed SmallVec had workload-dependent performance, we should have been more skeptical of using it again. The lesson transfers:
Profiling said "State cloning is 21.73%" but didn't tell us:
We jumped to SmallVec without understanding the distribution.
The only way to discover the workload variability issue was to:
Micro-optimization assumptions fail in complex systems.
Once size 12 showed universal regressions, we reverted immediately. No sunk cost fallacy:
Current performance is already excellent from Phase 3:
State cloning at 21.73% is acceptable given:
Priority 1: In-Place Mutation API
Priority 2: Arc<Vec> for Paths
Priority 3: Detailed State Size Profiling
SmallVec for State positions fails due to:
Final Decision: Revert to Vec<Position> and accept 21.73% State cloning overhead.
Current performance remains excellent. Further optimization requires different approaches (in-place mutation or path sharing).
Files Generated:
benchmark_results_phase4_size4.txt - Size 4 benchmark results (deleted)benchmark_results_phase4_size8.txt - Size 8 benchmark results (deleted)benchmark_results_phase4_size12.txt - Size 12 benchmark results (deleted)PHASE4_SMALLVEC_INVESTIGATION.md - This documentCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |