Date: 2025-11-12 Hardware: Intel Xeon E5-2699 v3 @ 2.30GHz (CPU Core 0, performance governor) Rust: 1.91.0 Baseline Commit: e5a32a0 Optimization Commit: (in progress)
Hypothesis CONFIRMED ✅ - Const array optimization delivers 15-28% improvement across all presets, significantly exceeding the 2% threshold.
Decision: KEEP - Replace original preset methods with const versions in production code.
Statement: Preset substitution sets have known, fixed contents at compile time. Using const arrays instead of runtime pair construction would eliminate hash computation overhead during initialization.
Expected Impact: 5-15% improvement for preset initialization
Actual Impact: 15-28% improvement (exceeded expectations!)
Created const array-based preset builders as an alternative to the original runtime construction:
// Original (runtime construction)
pub fn phonetic_basic() -> Self {
Self::from_pairs(&[
('f', 'p'), ('p', 'f'),
// ... converts chars to bytes, allocates FxHashSet, inserts pairs
])
}
// Optimized (const array)
const PHONETIC_PAIRS: &[(u8, u8)] = &[
(b'f', b'p'), (b'p', b'f'),
// ... compile-time const data
];
pub fn phonetic_basic_const() -> Self {
let mut set = Self::with_capacity(PHONETIC_PAIRS.len());
for &(a, b) in PHONETIC_PAIRS {
set.allow_byte(a, b); // Direct byte insertion, no char conversion
}
set
}
is_ascii() checks)from_pairs() creates temporary Vecsrc/transducer/substitution_set_const.rs: Const array implementation (198 lines)src/transducer/substitution_set_phf.rs: PHF attempt (identical to const, PHF doesn't support tuples)benches/substitution_set_microbench.rs: Added comparison benchmarks| Preset | Original (ns) | Const (ns) | Improvement | Speedup | p-value |
|---|---|---|---|---|---|
| phonetic_basic | 195.74 | 158.23 | -19.2% | 1.24x | < 0.05 |
| keyboard_qwerty | 586.71 | 495.12 | -15.6% | 1.18x | < 0.05 |
| leet_speak | 244.76 | 200.36 | -18.1% | 1.22x | < 0.05 |
| ocr_friendly | 223.64 | 160.49 | -28.2% | 1.39x | < 0.05 |
Average improvement: 20.3% across all byte presets Absolute time savings: 37-92ns per preset initialization
All measurements show:
| Preset | Pairs | Original (ns) | Const (ns) | ns/pair (orig) | ns/pair (const) |
|---|---|---|---|---|---|
| phonetic_basic | 14 | 195.74 | 158.23 | 13.98 | 11.30 |
| ocr_friendly | 16 | 223.64 | 160.49 | 13.98 | 10.03 |
| leet_speak | 22 | 244.76 | 200.36 | 11.13 | 9.11 |
| keyboard_qwerty | 68 | 586.71 | 495.12 | 8.63 | 7.28 |
Observation: Smaller presets show greater relative improvement (28% vs 15%), likely due to overhead being proportionally larger.
Eliminated char → byte conversion: Original from_pairs() takes &[(char, char)] and converts each to bytes
is_ascii() checks per pairDirect byte insertion: allow_byte() is faster than allow() (skips validation)
Better capacity estimation: Const array length is exact; original relies on slice length hint
Memory locality: Const arrays are in data segment; runtime construction allocates on heap
Both implementations performed nearly identically (±3-5%), confirming they use the same underlying approach:
| Preset | Const (ns) | PHF (ns) | Difference |
|---|---|---|---|
| phonetic_basic | 158.23 | 166.48 | +5.2% |
| keyboard_qwerty | 495.12 | 514.56 | +3.9% |
| leet_speak | 200.36 | 205.37 | +2.5% |
| ocr_friendly | 160.49 | 154.61 | -3.7% |
Conclusion: PHF v0.11 doesn't support tuple keys, so both use const arrays. Small differences are measurement noise.
OS: Linux 6.17.7-arch1-1
Rust: 1.91.0 (f8297e351 2025-10-28)
CPU: Intel Xeon E5-2699 v3 @ 2.30GHz
CPU Governor: performance
CPU Affinity: Core 0
RUSTFLAGS: -C target-cpu=native
# Run preset benchmarks only
RUSTFLAGS="-C target-cpu=native" taskset -c 0 \
cargo bench --bench substitution_set_microbench --features rand \
-- "presets" 2>&1 | tee /tmp/substitution_preset_comparison.txt
/tmp/substitution_preset_comparison.txtReplace the original phonetic_basic(), keyboard_qwerty(), etc. with const versions:
// Change from:
pub fn phonetic_basic() -> Self {
Self::from_pairs(&[('f', 'p'), ...])
}
// To:
pub fn phonetic_basic() -> Self {
let mut set = Self::with_capacity(PHONETIC_PAIRS.len());
for &(a, b) in PHONETIC_PAIRS {
set.allow_byte(a, b);
}
set
}
Rationale: 15-28% improvement with no downsides.
PHF crate adds dependency weight but provides no benefit for tuple keys. Remove from Cargo.toml.
Char-level presets also showed improvement (9-13%). Apply same optimization:
diacritics_latin: -10.2%greek_case_insensitive: -13.3%cyrillic_case_insensitive: -10.4%japanese_hiragana_katakana: -11.5%Consider lazy_static or once_cell to initialize presets once globally:
static PHONETIC: Lazy<SubstitutionSet> = Lazy::new(|| {
SubstitutionSet::phonetic_basic_const()
});
This would eliminate repeated initialization overhead entirely.
Hypothesis 1 is strongly confirmed. Const array optimization provides 15-28% initialization speedup with high statistical confidence. This significantly exceeds the 2% threshold for keeping an optimization.
Next Steps:
Generated: 2025-11-12 Analyzed by: Claude Code (Anthropic AI Assistant) Status: ✅ APPROVED FOR PRODUCTION
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 |