Date: 2025-11-11 Benchmark Platform: Intel Xeon E5-2699 v3 @ 2.30GHz (18 cores, 36 threads) Dictionary Backend: DynamicDawg CPU Affinity: Cores 0-17 (taskset -c 0-17)
This report compares two complementary approaches to Levenshtein automata:
Lazy Automata (Schulz & Mihov 2002, also called "Parameterized"):
src/transducer/Eager Automata (Mitankin 2005, also called "Universal"):
src/transducer/universal/The terms "lazy" and "eager" better describe when construction happens, making the fundamental trade-off more intuitive.
This report presents a comprehensive performance comparison between Parameterized Levenshtein Automata (current production implementation) and Universal Levenshtein Automata (recently optimized precomputed approach). The comparison was conducted across multiple dimensions: single query performance, batch queries, distance scaling, and dictionary size scaling.
Use Parameterized Automata for production dictionary queries. The universal automata are currently best suited for:
This benchmark measures the overhead of initializing and executing a single query, simulating the worst-case scenario where no amortization occurs.
| Approach | Time (mean) | Std Dev | Relative Performance |
|---|---|---|---|
| Parameterized | 263.24 µs | ±1.2 µs | Baseline (1.0×) |
| Universal | 809.05 µs | ±5.5 µs | 3.07× slower |
Parameterized wins decisively. The lazy construction approach is significantly faster than linear dictionary scanning, even for cold starts.
This benchmark evaluates how well each approach handles multiple queries, measuring amortization benefits.
| Approach | Time/Batch | Throughput | Relative Performance |
|---|---|---|---|
| Parameterized | 2.48 ms | 4.03 Kelem/s | Baseline (1.0×) |
| Universal | 7.52 ms | 1.33 Kelem/s | 3.03× slower |
| Approach | Time/Batch | Throughput | Relative Performance |
|---|---|---|---|
| Parameterized | 25.64 ms | 3.90 Kelem/s | Baseline (1.0×) |
| Universal | 78.83 ms | 1.27 Kelem/s | 3.07× slower |
| Approach | Time/Batch | Throughput | Relative Performance |
|---|---|---|---|
| Parameterized | 244.29 ms | 4.09 Kelem/s | Baseline (1.0×) |
| Universal | 793.90 ms | 1.26 Kelem/s | 3.25× slower |
Parameterized wins across all batch sizes. The universal approach shows no amortization advantage because each query still requires a full linear dictionary scan.
This benchmark evaluates how complexity grows as the maximum Levenshtein distance increases.
| Distance | Parameterized | Universal | Slowdown Factor |
|---|---|---|---|
| d=1 | 55.0 µs | 636.4 µs | 11.6× slower |
| d=2 | 265.2 µs | 790.2 µs | 2.98× slower |
| d=3 | 661.8 µs | 941.8 µs | 1.42× slower |
| d=4 | 949.3 µs | 1199 µs | 1.26× slower |
| Distance | Parameterized Growth | Universal Growth |
|---|---|---|
| d=1→2 | 4.82× slower | 1.24× slower |
| d=2→3 | 2.50× slower | 1.19× slower |
| d=3→4 | 1.43× slower | 1.27× slower |
Parameterized shows superlinear growth: State space complexity increases rapidly with distance
Universal shows sublinear growth: Fixed structure per distance, scales more predictably
Crossover point approaching: At d≥4, universal complexity growth is becoming competitive
Parameterized wins up to d=4, but gap narrows. For very high distances (d>5), universal automata may become competitive due to their more predictable scaling behavior.
This benchmark evaluates how each approach handles dictionaries of varying sizes.
| Dict Size | Parameterized | Universal | Throughput P. | Throughput U. | Slowdown |
|---|---|---|---|---|---|
| 100 | 49.3 µs | 81.3 µs | 2.03 Melem/s | 1.23 Melem/s | 1.65× |
| 1,000 | 258.1 µs | 799.4 µs | 3.88 Melem/s | 1.25 Melem/s | 3.10× |
| 10,000 | 1.03 ms | 7.83 ms | 9.71 Melem/s | 1.28 Melem/s | 7.60× |
| Dict Size | Parameterized Complexity | Universal Complexity |
|---|---|---|
| 100 | 49.3 µs (baseline) | 81.3 µs (baseline) |
| 1,000 | 5.2× increase (10× size) | 9.8× increase (10× size) |
| 10,000 | 4.0× increase (10× size) | 9.8× increase (10× size) |
Parameterized: Sub-linear scaling (O(log n) behavior from DAWG traversal)
Universal: Linear scaling (O(n) behavior from full dictionary scan)
Parameterized wins decisively and gap widens with scale. The DAWG-based traversal provides O(log n) complexity vs O(n) linear scan, making parameterized automata essential for large dictionaries.
This benchmark measures the raw performance of the universal automaton's accepts() primitive for single word-pair comparisons (no dictionary involved).
| Distance | accepts() Time | Operations/sec |
|---|---|---|
| d=1 | 339.3 ns | 2.95 million |
| d=2 | 471.2 ns | 2.12 million |
| d=3 | 490.4 ns | 2.04 million |
The universal automaton primitive is highly efficient and suitable for specialized use cases requiring word-pair distance checks without dictionary integration.
Memory Structure:
Total Memory:
Characteristics:
Memory Structure:
Total Memory:
Characteristics:
| Scenario | Parameterized | Universal | Winner |
|---|---|---|---|
| Small dict + d=1 | 70 KB | 80 KB | Tie |
| Small dict + d=2 | 70 KB | 120 KB | Parameterized |
| Large dict + d=1 | 1 MB | 1.02 MB | Tie |
| Large dict + d=4 | 1 MB | 1.4 MB | Parameterized |
Memory usage is comparable for both approaches, with parameterized having a slight advantage at higher distances due to fixed StatePool size vs growing automaton states.
✅ Production dictionary queries (primary use case) ✅ Large dictionaries (>1K terms) - O(log n) complexity advantage ✅ Batch processing (consistent 4 Melem/s throughput) ✅ Low to medium distances (d=1-3) - optimal performance ✅ Cache-sensitive applications - DAWG provides excellent locality ✅ General-purpose fuzzy search - proven production implementation
Performance Profile:
✅ Single word-pair distance checks (no dictionary) - 339-490 ns ✅ Research and prototyping - clean theoretical implementation ✅ Very high distances (d>5) - more predictable scaling ✅ Custom filtering logic - accepts() primitive as building block ✅ Parallel dictionary scanning (future optimization)
Performance Profile:
⚖️ Tiny dictionaries (<100 terms) - overhead dominates both approaches ⚖️ Single query, low distance (d=1, <100 terms) - microsecond differences ⚖️ Memory-constrained scenarios - both have similar footprint
Problem: Current implementation uses brute-force linear scan of dictionary terms.
Opportunity: Integrate universal automata with dictionary traversal to eliminate O(n) scan.
Potential Approach:
Expected Benefit:
Estimated Performance:
Opportunity: Universal automata with dictionary integration could enable parallel querying.
Approach:
Expected Benefit:
Opportunity: Vectorize the accepts() primitive using AVX2/SSE intrinsics.
Approach:
Expected Benefit:
Recommendation: Parameterized Automata
Reasoning:
Recommendation: Parameterized Automata
Reasoning:
Recommendation: Universal Automata (accepts() primitive)
Reasoning:
Recommendation: Universal Automata
Reasoning:
Recommendation: Monitor both approaches
Reasoning:
The comprehensive benchmark results demonstrate that Parameterized Levenshtein Automata are the clear choice for production dictionary queries, offering 2-10× performance advantages across all tested scenarios. The lazy state construction and efficient DAWG traversal provide superior scalability, particularly for large dictionaries.
Universal Levenshtein Automata, while currently slower for dictionary queries due to linear scanning, offer:
Future Work:
The current production recommendation is clear: use parameterized automata. However, universal automata represent a promising avenue for future optimization research.
System:
Build Configuration:
[profile.bench]
opt-level = 3
lto = true
codegen-units = 1
debug = true
Compiler Flags:
RUSTFLAGS="-C target-cpu=native"
CPU Affinity:
taskset -c 0-17 # Pin to cores 0-17 for consistency
Dictionary Backend:
Benchmark Tool:
Result Files:
/tmp/parameterized_vs_universal_benchmark.txttarget/criterion/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 |