Liking cljdoc? Tell your friends :D

H4-H6 Optimization Evaluation

Date: 2025-11-12 Status: Evaluation phase - determining if pursuit is justified Baseline: H1 + H3 (current optimized state)


Executive Summary

Recommendation: ❌ DO NOT PURSUE H4-H6 at this time

Rationale:

  1. Diminishing returns: Expected gains <3% vs already achieved 9-46%
  2. Complexity cost: Platform-specific code, feature gating, maintenance burden
  3. Current state: Already excellent performance with H1 + H3
  4. ROI insufficient: Cost/benefit analysis strongly favors rejection

Decision: Reject H4-H6 for the current architecture unless production profiling reveals new bottlenecks


Current Performance Baseline (H1 + H3)

Achieved Optimizations

  • H1: 15-28% faster preset initialization
  • H3: 9-46% faster for small sets, 50-79% memory reduction
  • Integration: 84% of tests improved, zero critical regressions
  • Test Coverage: All 509 tests passing

Remaining Headroom

Based on crossover analysis and profiling:

  • Linear scan (1-3 pairs): Already optimal for this use case (cache-friendly sequential access)
  • Hash lookup (5+ pairs): FxHashSet is already highly optimized (~370ns constant time)
  • Preset initialization: Const arrays eliminate most overhead (compile-time)

Analysis: Limited remaining headroom for further optimization without fundamental algorithm changes.


H4: SIMD Lookup for Small Sets

Hypothesis

For very small sets (≤8 pairs), SIMD parallel comparison using AVX2 instructions could outperform linear scan by checking multiple pairs simultaneously.

Expected Impact

Optimistic: 1-3% improvement for 1-4 pair sets Realistic: <1% improvement (likely within noise)

Theoretical Analysis

Current Performance (H3 linear scan):

  • 1 pair: 201.3ns (baseline = 376.7ns, 1.87× speedup already achieved)
  • 2 pairs: 263.3ns (baseline = 366.8ns, 1.39× speedup)
  • 3 pairs: 330.8ns (baseline = 363.6ns, 1.10× speedup)

SIMD Potential:

  • AVX2 can process 4× u64 comparisons in parallel (256-bit registers)
  • Each (u8, u8) pair = 16 bits → can fit 16 pairs in 256 bits
  • Problem: Unpacking, shuffling, and comparison overhead

Break-Even Analysis:

Linear scan cost: N × 1.0ns per pair (sequential comparison)
SIMD setup cost: ~5-10ns (load, shuffle, prepare mask)
SIMD comparison: ~1-2ns (parallel compare + extract result)

For 1 pair: Linear = 1.0ns, SIMD = 7-12ns → Linear wins by 7-12×
For 4 pairs: Linear = 4.0ns, SIMD = 7-12ns → Linear still wins
For 8 pairs: Linear = 8.0ns, SIMD = 7-12ns → SIMD competitive at best

Conclusion: SIMD overhead likely negates any parallel benefit for small sets.

Implementation Complexity

Required Changes:

  • Platform detection (cfg(target_feature = "avx2"))
  • SIMD intrinsics (std::arch::x86_64)
  • Fallback implementation for non-AVX2 platforms
  • Data layout changes (alignment, padding)
  • Feature flag (simd feature already exists but unused)

Code Complexity: +150-200 LOC Maintenance Burden: High (platform-specific, CPU feature detection) Testing Burden: Multiple code paths (AVX2, SSE4.1, fallback)

Cost/Benefit Analysis

FactorWeightScore (1-5)WeightedNotes
Expected Improvement40%10.4<1% realistic
Code Complexity20%10.2+150-200 LOC, platform-specific
Maintenance20%10.2High burden (multiple paths)
Testing10%20.2Need multi-platform CI
Portability10%10.1x86-64 only, feature detection
Total100%1.1/5REJECT

Threshold for pursuit: 3.0/5 H4 score: 1.1/5Strong reject

Recommendation

DO NOT PURSUE H4

Rationale:

  1. Setup overhead likely exceeds parallel benefit for small sets
  2. H3 linear scan already cache-optimal (sequential access)
  3. Adds significant complexity for <1% realistic gain
  4. Platform-specific code reduces portability
  5. Not worth engineering effort given current excellent performance

H5: Perfect Hashing for Presets

Hypothesis

Compile-time perfect hash function for fixed presets eliminates runtime hash computation entirely, potentially faster than even const array initialization.

Expected Impact

Optimistic: 1-2% improvement for preset initialization Realistic: <0.5% improvement (likely within noise)

Theoretical Analysis

Current Performance (H1 const arrays):

  • phonetic_basic: 158ns (14 pairs)
  • keyboard_qwerty: 495ns (68 pairs)
  • leet_speak: 200ns (22 pairs)
  • ocr_friendly: 160ns (18 pairs)

Perfect Hashing Potential:

  • Eliminate FxHash computation (~3-5ns per pair)
  • Compile-time hash table generation
  • O(1) lookup with zero collisions

Best-Case Savings:

phonetic_basic: 14 pairs × 3ns = 42ns saved → 158ns - 42ns = 116ns (26% gain)
keyboard_qwerty: 68 pairs × 3ns = 204ns saved → 495ns - 204ns = 291ns (41% gain)

Reality Check:

  • H1 already uses direct byte insertion (allow_byte() vs allow())
  • Const arrays pre-allocate exact capacity
  • Most initialization cost is memory allocation, not hashing
  • Perfect hash table still requires allocation + initialization

Realistic Savings: <10% of initialization time → <1% end-to-end

Implementation Complexity

Required Tools:

  • Build-time perfect hash generation (phf crate)
  • Code generation in build.rs
  • Const generic perfect hash functions
  • Integration with existing preset definitions

Code Complexity: +100-150 LOC (build.rs + codegen) Build-Time Cost: Increased compile time Maintenance Burden: Medium (build-time dependencies)

Critical Issue: Initialization Still Required

Problem: Perfect hash only optimizes lookup, not initialization

// Current (H1):
const PHONETIC_PAIRS: &[(u8, u8)] = &[...];
SubstitutionSet::from_pairs(PHONETIC_PAIRS);  // 158ns

// Perfect hash (H5):
const PHONETIC_HASH: PerfectHashMap<(u8, u8), ()> = ...;
// Still need to convert to SubstitutionSet → same initialization cost!

Conclusion: Perfect hashing doesn't address the right bottleneck. Initialization cost dominates, not lookup during init.

Cost/Benefit Analysis

FactorWeightScore (1-5)WeightedNotes
Expected Improvement40%10.4<0.5% realistic
Code Complexity20%20.4Build-time codegen
Maintenance20%20.4phf dependency
Build-Time Cost10%20.2Increased compile time
Applicability10%20.2Presets only (not user sets)
Total100%1.6/5REJECT

Threshold for pursuit: 3.0/5 H5 score: 1.6/5Reject

Recommendation

DO NOT PURSUE H5

Rationale:

  1. Doesn't address initialization bottleneck (memory allocation dominates)
  2. H1 already eliminates most overhead with const arrays
  3. <1% realistic gain for significant build-time complexity
  4. Only applies to presets (not user-defined sets)
  5. phf dependency adds maintenance burden

H6: Custom Hasher Optimization

Hypothesis

Specialized hasher for (u8, u8) pairs could reduce collisions and improve performance over general-purpose FxHasher.

Expected Impact

Optimistic: 1-2% improvement for hash-based lookups Realistic: <0.5% improvement (FxHasher already excellent for small keys)

Theoretical Analysis

Current Performance (FxHashSet with FxHasher):

  • Hash lookup: ~370ns (constant across 5-20 pairs)
  • FxHasher: Optimized for small keys, very low collision rate

Custom Hasher Potential:

  • (u8, u8) = 16 bits → trivial to hash
  • Could use simple XOR or multiply-shift
  • Perfect hashing possible for small domains

Best-Case Savings:

FxHash computation: ~3-5ns
Custom hash: ~1-2ns
Savings: ~2-3ns per lookup

For typical query (100 lookups):
Current: 100 × 5ns = 500ns
Optimized: 100 × 3ns = 300ns
Savings: 200ns (40% of hash time)

But hash time is only part of total query time!
Total query time: ~10-50µs
Hash savings: 200ns = 0.2-2% of total

Conclusion: Hash computation is tiny fraction of total query time.

Implementation Complexity

Required Changes:

struct PairHasher;

impl Hasher for PairHasher {
    fn write(&mut self, bytes: &[u8]) { /* custom logic */ }
    fn write_u16(&mut self, i: u16) { /* optimized for (u8, u8) */ }
    fn finish(&self) -> u64 { /* return hash */ }
}

impl BuildHasher for PairHasherBuilder {
    type Hasher = PairHasher;
    fn build_hasher(&self) -> Self::Hasher { /* ... */ }
}

// Usage:
FxHashSet::with_hasher(PairHasherBuilder);

Code Complexity: +50-100 LOC Maintenance Burden: Medium (custom hasher logic) Testing Burden: Need collision testing, distribution analysis

Critical Issue: FxHasher Already Excellent

FxHasher Characteristics:

  • Designed for small keys (exactly our use case)
  • Very fast (~3-5ns for small keys)
  • Low collision rate
  • Well-tested, production-proven

Custom Hasher Challenges:

  • Hard to beat FxHasher for 16-bit keys
  • Risk of worse collision behavior
  • Need extensive testing/validation
  • Maintenance burden for marginal gain

Cost/Benefit Analysis

FactorWeightScore (1-5)WeightedNotes
Expected Improvement40%10.4<0.5% end-to-end
Code Complexity20%30.6+50-100 LOC, but straightforward
Maintenance20%20.4Custom hasher logic
Testing10%20.2Need collision analysis
Risk10%20.2Worse collisions possible
Total100%1.8/5REJECT

Threshold for pursuit: 3.0/5 H6 score: 1.8/5Reject

Recommendation

DO NOT PURSUE H6

Rationale:

  1. FxHasher already excellent for small keys
  2. Hash time is tiny fraction of total query time (<2%)
  3. Risk of worse collision behavior
  4. Hard to beat well-tested production hasher
  5. <0.5% realistic end-to-end gain

Summary Comparison

HypothesisExpected GainComplexityScoreDecision
H1 (Const Arrays)15-28%Low (+50 LOC)4.5/5ACCEPTED
H2 (Bitmap)-400% initMedium (+200 LOC)1.0/5REJECTED
H3 (Hybrid)9-46%Medium (+70 LOC)4.75/5ACCEPTED
H4 (SIMD)<1%High (+150-200 LOC)1.1/5REJECT
H5 (Perfect Hash)<0.5%Medium (+100-150 LOC)1.6/5REJECT
H6 (Custom Hasher)<0.5%Medium (+50-100 LOC)1.8/5REJECT

Acceptance Threshold: 3.0/5

Results:

  • Accepted: H1, H3 (both exceeded expectations)
  • Rejected: H2 (initialization cost), H4-H6 (insufficient ROI)

Decision: Defer H4-H6 Indefinitely

Rationale

  1. Excellent Current State

    • H1 + H3 deliver 9-46% improvements
    • Zero critical regressions
    • All tests passing
    • Production-ready
  2. Diminishing Returns

    • H4-H6 expected gains: <3% combined
    • Already achieved major wins (15-46%)
    • Limited remaining headroom
  3. Cost/Benefit

    • All H4-H6 score below 2.0/5 (threshold: 3.0/5)
    • High complexity for minimal gain
    • Maintenance burden not justified
  4. Better Alternatives

    • Monitor production metrics
    • Profile real-world usage patterns
    • Address actual bottlenecks if they emerge
    • Consider algorithmic changes if needed (not micro-optimizations)

Conditions for Reconsidering

H4-H6 should ONLY be reconsidered if:

  1. Production profiling reveals:

    • SubstitutionSet is >20% of total query time
    • Users demand sub-nanosecond latency
    • Specific use cases justify SIMD/perfect-hash complexity
  2. Substantial resources available:

    • Dedicated optimization team
    • Cross-platform CI infrastructure
    • Budget for extensive testing/validation
  3. New algorithmic insights:

    • Better SIMD approach discovered
    • Zero-overhead perfect hashing technique
    • Hash function breakthrough

Current Status: None of these conditions are met → REJECT FOR CURRENT ARCHITECTURE


Recommendations

Immediate Actions

  1. Deploy H1 + H3 to production - Both optimizations ready
  2. Monitor real-world metrics - Validate improvements in production
  3. Archive H4-H6 analysis - Document for future reference
  4. Close optimization project - Declare success

Long-Term Strategy

  1. Production Monitoring

    • Track query latency percentiles (p50, p95, p99)
    • Measure actual substitution set size distribution
    • Profile real-world workloads
  2. Iterative Improvement

    • Only optimize based on production evidence
    • Focus on actual bottlenecks (not speculative)
    • Data-driven decision making
  3. Algorithmic Innovation

    • Consider fundamentally different approaches if needed
    • Research literature for new algorithms
    • Collaborate with performance experts if justified

Conclusion

The SubstitutionSet optimization project has successfully delivered two production-ready optimizations (H1, H3) with 9-46% improvements. Further micro-optimizations (H4-H6) are not justified given:

  1. Insufficient ROI: <3% expected gains for significant complexity
  2. Excellent current state: Already achieved major wins
  3. Better alternatives: Monitor production, address real bottlenecks

Final Decision: ❌ REJECT H4-H6 FOR CURRENT ARCHITECTURE

Status: Optimization project COMPLETE - Mission accomplished with H1 + H3 ✅


Document Version: 1.0 Last Updated: 2025-11-12 Author: Claude Code (Anthropic AI Assistant) Decision: H4-H6 rejected for the current architecture; optimization project closed successfully

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close