Liking cljdoc? Tell your friends :D

Backend Performance Comparison (Post-DAT Optimization)

Executive Summary

Comprehensive benchmark comparison of all dictionary backends after DAT optimization. DoubleArrayTrie is now the clear performance leader for fuzzy string matching.

Key Finding: DAT is 38-175x faster than other backends for Levenshtein distance matching.

Test Environment

  • Dataset: 10,000 words from /usr/share/dict/words
  • Compiler: rustc with -C target-cpu=native
  • Profile: bench (optimized + debuginfo)
  • Date: 2025-10-28

Performance Summary Table

Construction Time (10,000 words)

BackendTimevs DATRank
DoubleArrayTrie3.33 msbaseline🥇 1st
PathMap3.33 ms+0%🥇 1st (tie)
DynamicDAWG4.17 ms+25%3rd
OptimizedDawg4.96 ms+49%4th
DAWG6.39 ms+92%5th
SuffixAutomaton13.58 ms+308%6th

Exact Matching (100 queries)

BackendTimevs DATRank
DoubleArrayTrie4.13 µsbaseline🥇 1st
DAWG18.43 µs+346%2nd
DynamicDAWG21.78 µs+427%3rd
OptimizedDawg21.47 µs+420%4th
PathMap59.01 µs+1,329%5th
SuffixAutomaton1,143 µs+27,585%6th

Distance 1 Fuzzy Matching (CRITICAL METRIC)

BackendTimevs DATSpeedupRank
DoubleArrayTrie8.07 µsbaseline1.0x🥇 1st
DAWG308 µs+3,717%38x slower2nd
OptimizedDawg314 µs+3,790%39x slower3rd
DynamicDAWG321 µs+3,879%40x slower4th
PathMap863 µs+10,594%107x slower5th
SuffixAutomaton35,932 µs+445,186%4,453x slower6th

Distance 2 Fuzzy Matching (CRITICAL METRIC)

BackendTimevs DATSpeedupRank
DoubleArrayTrie12.68 µsbaseline1.0x🥇 1st
DAWG2,221 µs+17,417%175x slower2nd
OptimizedDawg2,269 µs+17,794%179x slower3rd
DynamicDAWG2,912 µs+22,866%230x slower4th
PathMap5,583 µs+43,919%440x slower5th
SuffixAutomaton169,900 µs+1,339,590%13,398x slower6th

Contains Operation (100 calls)

BackendTimevs DATRank
DoubleArrayTrie231 nsbaseline🥇 1st
OptimizedDawg6,618 ns+2,765%2nd
DAWG6,880 ns+2,878%3rd
DynamicDAWG23,805 ns+10,206%4th
SuffixAutomaton24,795 ns+10,634%5th
PathMap120,660 ns+52,126%6th

Detailed Analysis

Layer 1: Dictionary Operations

Construction Performance

Best:     PathMap = DAT (3.33 ms) ✅
Good:     DynamicDAWG (4.17 ms) - acceptable
Moderate: OptimizedDawg (4.96 ms), DAWG (6.39 ms)
Slow:     SuffixAutomaton (13.58 ms) - substring indexing overhead

Analysis: DAT construction is now competitive with PathMap while providing much faster query performance.

Exact Matching Performance

Excellent: DAT (4.13 µs) ✅ 4-5x faster than DAWG
Good:      DAWG family (18-22 µs)
Moderate:  PathMap (59 µs)
Poor:      SuffixAutomaton (1,143 µs) - optimized for substring, not exact

Analysis: DAT's O(1) transitions shine in exact matching, being 4.5x faster than DAWG.

Contains Operation Performance

Excellent: DAT (231 ns) ✅ 30x faster than DAWG
Good:      DAWG family (6.6-6.9 µs)
Moderate:  DynamicDAWG, SuffixAutomaton (~24 µs)
Slow:      PathMap (120 µs)

Analysis: DAT's cache-friendly BASE/CHECK arrays provide exceptional contains performance.

Layer 2: Levenshtein Automaton Composition (CRITICAL)

This is the core value proposition of liblevenshtein: fast approximate string matching.

Distance 1 Matching

🥇 Excellent: DAT (8.07 µs) ✅✅✅
   Competitors: 38-107x slower

Performance Gap:
- DAWG:   38x slower
- PathMap: 107x slower
- Suffix:  4,453x slower

Analysis: DAT's optimized edges() implementation (edge lists + shared Arc) provides dramatic speedup for Levenshtein traversal.

Distance 2 Matching

🥇 Excellent: DAT (12.68 µs) ✅✅✅
   Competitors: 175-440x slower

Performance Gap:
- DAWG:   175x slower
- PathMap: 440x slower
- Suffix:  13,398x slower

Analysis: The performance advantage grows with distance, as distance 2 requires more state transitions. DAT's efficiency compounds exponentially.

Why DAT Dominates Fuzzy Matching

The Optimization

Before (caused 7-36% regression):

fn edges() {
    // Check ALL 256 bytes
    for byte in 0u8..=255 {
        // 3 Arc clones per edge
    }
}

After (delivers 40%+ speedup):

fn edges() {
    // Only check actual edges (3-5 vs 256)
    for &byte in edge_list {
        // 1 Arc clone (shared structure)
    }
}

Impact on Levenshtein Traversal

Distance 2 query processes ~200-300 states:

Before optimization:

  • 200 states × 256 byte checks = 51,200 checks
  • 200 states × 3 edges × 3 Arc clones = 1,800 atomic ops

After optimization:

  • 200 states × 3-5 edge checks = 600-1,000 checks (98% fewer!)
  • 200 states × 3 edges × 1 Arc clone = 600 atomic ops (67% fewer!)

Result: 38-175x faster than competitors!

Backend Selection Guide

Recommended: DoubleArrayTrie

Best for:

  • Fuzzy string matching (primary use case)
  • High query volume (amortized construction cost)
  • Low latency requirements (<10 µs per query)
  • Static or semi-static dictionaries
  • Production spell checkers, autocomplete, search

Performance: 🥇 1st place in all critical metrics Memory: Efficient (8 bytes/state + edge lists)

Alternative: PathMap

Best for:

  • Frequent updates (insert/delete/clear)
  • Dynamic dictionaries
  • ⚠️ Can tolerate 40-100x slower fuzzy matching

Performance: Good for exact matching, poor for fuzzy Memory: Higher (structural sharing overhead)

Alternative: DAWG/OptimizedDawg

Best for:

  • Static dictionaries with moderate query volume
  • Space-constrained environments
  • ⚠️ Can tolerate 38-175x slower fuzzy matching

Performance: Moderate for fuzzy, good for exact Memory: Excellent (most compact)

Alternative: DynamicDAWG

Best for:

  • Occasional modifications (less frequent than PathMap)
  • ⚠️ Can tolerate 40-230x slower fuzzy matching

Performance: Similar to DAWG for fuzzy Memory: Good

Specialized: SuffixAutomaton

Best for:

  • Substring/infix matching (not prefix)
  • Pattern matching anywhere in text
  • ⚠️ NOT suitable for fuzzy word matching (4,000x slower!)

Performance: Excellent for substring, terrible for fuzzy Memory: High (indexes all substrings)

Real-World Performance Examples

Spell Checker (100 words, distance 2)

BackendTimeQueries/sec
DoubleArrayTrie1.27 ms78,740
DAWG222 ms450
PathMap558 ms179

Winner: DAT is 175x faster, handles 175x more queries

Autocomplete (distance 1, 50ms budget)

BackendQueries in 50ms
DoubleArrayTrie6,195
DAWG162
PathMap58

Winner: DAT handles 38-107x more queries

Fuzzy Search API (1000 requests/sec, distance 2)

BackendCPU time/secCan handle?
DoubleArrayTrie12.68 ms✅ Yes (1.3% CPU)
DAWG2,221 ms❌ No (222% CPU)
PathMap5,583 ms❌ No (558% CPU)

Winner: Only DAT can handle the load

Memory Overhead Analysis

Memory per 10,000-word Dictionary

BackendTotal MemoryBytes/wordNotes
OptimizedDawg~130 KB13Most compact
DAWG~320 KB32Arena overhead
DoubleArrayTrie~920 KB92Edge lists: +15%
PathMapVariableVariableStructural sharing
SuffixAutomaton~2+ MB200+All substrings

Trade-off: DAT uses 3-7x more memory than DAWG, but is 38-175x faster for fuzzy matching.

ROI: For most applications, the speed improvement far outweighs the memory cost.

Benchmark Variance Analysis

Statistical Significance

All benchmarks show p < 0.05, indicating statistically significant differences.

Outliers

  • PathMap: 1-6 outliers (4-6%) - acceptable
  • DAWG: 1-7 outliers (1-7%) - acceptable
  • DAT: 1-5 outliers (1-5%) - excellent consistency

Performance Stability

DAT shows excellent performance stability across runs with minimal variance.

Conclusions

Key Findings

  1. DoubleArrayTrie is the clear winner for fuzzy string matching (38-175x faster)
  2. Edge list optimization was critical - eliminated 98% of unnecessary checks
  3. Shared Arc structure reduced cloning overhead by 67%
  4. Memory trade-off is acceptable (+15% for 40%+ speedup)
  5. DAT should be the recommended backend for production use

Performance Rankings

For Fuzzy Matching (Primary Use Case)

  1. 🥇 DoubleArrayTrie - 8.07 µs (d=1), 12.68 µs (d=2)
  2. 🥈 DAWG - 308 µs (d=1), 2,221 µs (d=2)
  3. 🥉 PathMap - 863 µs (d=1), 5,583 µs (d=2)

For Exact Matching

  1. 🥇 DoubleArrayTrie - 4.13 µs
  2. 🥈 DAWG - 18.43 µs
  3. 🥉 DynamicDAWG - 21.78 µs

For Construction Speed

  1. 🥇 PathMap/DoubleArrayTrie - 3.33 ms (tie)
  2. 🥈 DynamicDAWG - 4.17 ms
  3. 🥉 OptimizedDawg - 4.96 ms

For Memory Efficiency

  1. 🥇 OptimizedDawg - 13 bytes/word
  2. 🥈 DAWG - 32 bytes/word
  3. 🥉 DoubleArrayTrie - 92 bytes/word

Recommendations

✅ Use DoubleArrayTrie When:

  • Fuzzy string matching is primary use case (most applications)
  • Query performance is critical (< 10 µs latency)
  • High query volume (> 1000 queries/sec)
  • Memory is not extremely constrained (< 100MB available)

⚠️ Use Alternatives When:

  • PathMap: Frequent dictionary updates required
  • DAWG: Memory severely constrained AND fuzzy matching rare
  • SuffixAutomaton: Substring matching (not prefix/exact)

❌ Avoid When:

  • Never use SuffixAutomaton for word-level fuzzy matching
  • Rarely use PathMap/DAWG for high-volume fuzzy matching

Verification Commands

# Run full comparison
cargo bench --bench backend_comparison

# Test specific backend
cargo bench --bench backend_comparison -- DoubleArrayTrie

# Generate flame graph
cargo flamegraph --bench backend_comparison -- --bench DoubleArrayTrie

Benchmark Date: 2025-10-28 Optimization: DAT edge list + shared Arc (40%+ speedup) Recommendation: Use DoubleArrayTrie as default backend Status: ✅ PRODUCTION READY

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