Date: November 18, 2025, 00:42 EST System: Intel Xeon E5-2699 v3 @ 2.30GHz, CPU pinned to core 0 Tool: cargo-flamegraph + perf Samples: 639,127 samples, 2.09 trillion cycles
Key Finding: The performance is distributed across multiple operations rather than dominated by a single bottleneck. The top 5 hotspots in actual automaton code account for ~26% of cycles, with Criterion benchmark overhead consuming ~20%.
Critical Discovery: Subsumption (H3) is NOT the primary bottleneck at 0.71%, contradicting our initial hypothesis. The real costs are:
Location: src/transducer/generalized/automaton.rs
Function: Main acceptance function that orchestrates the automaton
Breakdown:
GeneralizedState::transition (state transitions)subsumes_standard (subsumption checking)Iterator::advance_by (for relevant_subword)next_code_point (UTF-8 character iteration)Analysis: This is the main entry point, so high percentage is expected. The cost is distributed across many small operations rather than one large bottleneck.
Optimization Potential: Medium - Distributed costs are harder to optimize than concentrated bottlenecks.
Location: src/transducer/generalized/state.rs
Function: Generates successor states for all operations
Breakdown:
successors_i_type (I-type transitions: insertions)Iterator::next (iterating over positions)GeneralizedState::transition callsKey Insight: successors_i_type is the single largest function at 2.75% of total cycles.
Analysis: This function is called repeatedly during BFS traversal. The 320-line successors_i_type method generates all insertion successors and involves:
Optimization Potential: HIGH - Largest single function hotspot. Target for H1 optimization.
Location: libc.so.6
Function: free() - memory deallocation
What's Being Freed:
successors_i_type operationsVec<char> cleanup (character vectors)Vec<bool> cleanup (CharacteristicVector)successors_i_type cleanupAnalysis: Every call to successors_i_type allocates temporary Vec<char> for character sequences (via char().collect()), which must then be freed. This is H2 (repeated char().collect()) manifesting as deallocation cost.
Optimization Potential: HIGH - Can be eliminated by caching character vectors (H2 optimization).
Location: Core library Function: Collecting iterators into collections (Vec)
What's Being Collected:
str::chars().collect::<Vec<char>>()successors_i_type for each positionAnalysis: This is the allocation counterpart to #3 (deallocation). Together they represent 8.47% of cycles spent on temporary character vector management.
Optimization Potential: HIGH - Direct target for H2 (cache character vectors).
Location: src/transducer/generalized/subsumption.rs
Function: Checks if one position subsumes another
Analysis: Despite our hypothesis H3 predicting O(n²) bottleneck, subsumption accounts for less than 1% of cycles in these benchmarks.
Why Low Impact?:
Note: The max_state benchmarks showed 19x slowdown at distance 3, confirming O(n²) scaling exists but isn't dominant in typical usage.
Optimization Potential: MEDIUM - Important for edge cases (high distance, long words) but not typical workloads.
Status: PARTIALLY CONFIRMED
Data: successors (7.77%) + overhead in accepts (≈2-3%) = ~10-11%
Revised: Successor generation is the largest component but doesn't dominate (it's ~10-15% not 60-80%). The actual cost is distributed across many operations.
Status: CONFIRMED Data: Iterator::collect (4.08%) + cfree for Vec cleanup (≈2%) = ~6-8% Impact: Within predicted range. High optimization potential.
Status: REJECTED for typical workloads Data: subsumes_standard = 0.71% (far less than predicted) Caveat: Benchmarks confirmed 19x slowdown exists for max-state at distance 3, but this is an edge case not typical usage.
Status: PARTIALLY VISIBLE
Data: OperationType::can_apply = 0.79%
Analysis: Lower than predicted but phonetic operations not yet benchmarked. Defer to Phase 2.
Status: NOT YET TESTED (Phase 2)
Status: NOT YET TESTED (Phase 2)
Criterion Statistical Analysis: ~20% of total cycles
exp (exponential function for KDE)rayon parallel processingImplication: The actual automaton code is only consuming ~80% of measured cycles. Benchmark overhead is significant but expected for Criterion's robust statistical analysis.
The performance is distributed:
Implication: Optimization will require multiple targeted improvements rather than fixing one major issue.
Combined allocation + deallocation overhead (8.47%) is comparable to successor generation (7.77%).
Root Cause: Temporary Vec<char> allocations in successors_i_type.
Solution: Cache character vectors per word (H2 optimization).
At 2.75%, this 320-line method is the clear winner for targeted optimization.
Optimization Opportunities:
At 0.71%, subsumption is efficient for typical workloads.
However: The 19x slowdown at max-state confirms that O(n²) scaling exists for pathological cases.
Strategy: Defer subsumption optimization (H3) until after addressing H2 (char vector caching).
Character iteration (next_code_point, advance_by) accounts for ~1.15%.
Analysis: UTF-8 validation and decoding has inherent cost. Acceptable given Unicode correctness requirements.
Optimization: Low priority - correctness > marginal performance gain.
Based on flamegraph data, revised optimization order:
Target: 8.47% (collection + deallocation)
Implementation: Pre-compute word.chars().collect::<Vec<char>>() once per accepts() call
Expected Improvement: 5-8% speedup
Effort: Low (simple refactor)
Target: 2.75% direct + additional overhead Implementation:
Expected Improvement: 2-4% additional speedup Effort: Medium (requires careful profiling)
Target: Reduce heap allocations (currently manifesting in cfree overhead) Implementation: Benchmark optimal inline capacities for SmallVec Expected Improvement: 1-2% speedup Effort: Low (configuration changes + benchmarks)
Target: 0.71% (typical) to 19x edge case Reasoning: Not impactful for typical workloads Implementation: Only if edge cases become priority Effort: High (algorithmic changes)
baseline_standard.svg (369 KB)
flamegraph_analysis.txt
flamegraph_hotspots.txt
perf.data (39.2 GB)
phonetic_* benchmark targets for operation and acceptance scenariosPhase 1.3 Complete: Flamegraph successfully generated and analyzed.
Major Discovery: The performance bottleneck is distributed memory management (8.47% in allocation/deallocation) rather than a single algorithmic issue. The path forward is clear:
Key Metric: These three optimizations target ~15-20% of current cycle consumption, with realistic potential for 10-15% overall speedup.
Hypothesis Revision: Subsumption (H3) is not the bottleneck for typical workloads. It remains important for edge cases but is low priority.
Flamegraph Location: docs/optimization/flamegraphs/baseline_standard.svg
Analysis Tools: perf (Linux), cargo-flamegraph
Next Phase: Phase 2 - Phonetic Operations Profiling
Can 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 |