Liking cljdoc? Tell your friends :D

Benchmark Results: January 2026 Improvements

This document presents benchmark results for recently implemented performance improvements in liblevenshtein-rust.

Hardware Specifications

  • CPU: Intel Xeon E5-2699 v3 @ 2.30GHz (36 cores / 72 threads)
  • RAM: 252GB DDR4-2133 ECC Registered (8x 32GB Micron DIMMs)
  • Storage: Samsung SSD 990 PRO 4TB NVMe
  • OS: Arch Linux (kernel 6.18.3)
  • Rust: rustc 1.84.0+

Overview

ModuleIntegrationImprovementUse Case
Myers' Bit-ParallelStandalone5.7x-15.2x speedupPairwise distance computation
N-gram IndexStandaloneO(n) candidate filteringApplication-level pre-filtering
Jaro-WinklerStandalone~95-138 MiB/sSimilarity metric utility
Hybrid MatcherStandaloneAlternative to automatonApplication-level filtering
Priority QueryIntegrated1.9-4.0x for first-KA*-ordered automaton traversal
Articulatory DistanceStandalonePhonetic awarenessPairwise distance with phonetic costs

Note on Integration: Only PriorityQueryIterator is integrated into the core automata pipeline. The other modules are standalone utilities that applications can use independently or compose with the automata as needed.


1. Myers' Bit-Parallel Algorithm (Reference Implementation)

Myers' algorithm uses bit-parallel operations to compute Levenshtein distance in O(ceil(m/64) * n) time.

Note: This improvement applies to the standalone distance computation API (liblevenshtein::distance), not to the core Levenshtein automata. The automaton-based dictionary search does not compute pairwise distances—it traverses automaton states in lockstep with the dictionary trie. Myers' algorithm is provided as a fast reference implementation for users who need direct string-to-string distance computation outside of dictionary search.

Performance vs Standard DP

String LengthMyersStandard DPSpeedup
8 chars135 ns167 ns1.2x
16 chars133 ns758 ns5.7x
64 chars340 ns5.17 µs15.2x

Key Findings

  • Optimal for strings ≤64 characters where pattern fits in single 64-bit word
  • Linear scaling beyond 64 chars with multi-word processing
  • Bounded search with early termination is faster for close matches (117-134 ns)

Recommendation

Use Myers' algorithm for:

  • Standalone distance queries: distance::myers_distance("foo", "bar")
  • Batch processing of many string pairs
  • Verification of candidate matches from external sources
  • Applications where you have two specific strings and need their edit distance

Not applicable for: Dictionary-based fuzzy search (use the Levenshtein automata instead)


2. N-gram Index Pre-filtering (Standalone Utility)

The N-gram index provides fast candidate filtering by matching character n-grams between query and dictionary terms.

Note: This is a standalone utility exported via liblevenshtein::filter::NgramIndex. It is NOT integrated into the transducer query pipeline—applications must explicitly use it for pre-filtering before (or instead of) automaton traversal.

Construction Time

Dictionary SizeBigramTrigram
1,000 terms0.82 msN/A
10,000 terms8.6 msN/A
50,000 terms57.5 msN/A

Throughput: ~1.1-1.2 M terms/sec for construction.

Query Time (50,000 term dictionary)

Query Typed=1d=2d=3
Short ("test")44.7 µs53.9 µs67.0 µs
Typo ("progamming")36.9 µs61.3 µs104.7 µs
Long ("acknowledgement")77.9 µs119.5 µs220.7 µs

Key Findings

  • Constant-time per-query regardless of dictionary size (after construction)
  • Sub-millisecond filtering enables real-time applications
  • Rejection rate depends on query specificity and distance threshold

3. Jaro-Winkler Similarity (Standalone Utility)

Jaro and Jaro-Winkler provide string similarity metrics optimized for name matching.

Note: This is a standalone utility exported via liblevenshtein::filter::{jaro_similarity, jaro_winkler_similarity}. These are similarity metrics (not edit distance), useful for name matching and record linkage independent of the automata.

Similarity Computation

Test CaseJaroJaro-WinklerThroughput
Identical (short)18 ns49 ns138 MiB/s
Similar (short)77 ns110 ns69 MiB/s
Different (short)51 ns84 ns68 MiB/s
Classic "MARTHA/MARHTA"291 ns381 ns30 MiB/s
Unicode ("cafe/cafe")56 ns90 ns94 MiB/s

Key Findings

  • Jaro is ~37% faster than Jaro-Winkler on average
  • Winkler prefix boost adds ~33% overhead but improves accuracy for names
  • Unicode-aware with proper handling of multi-byte characters
  • Best for: Name matching, record linkage, fuzzy deduplication

4. Hybrid Matcher (Standalone Alternative to Automata)

Combines N-gram filtering with Jaro-Winkler verification for accurate candidate selection.

Note: This is a standalone utility exported via liblevenshtein::filter::HybridMatcher. It is NOT integrated into the transducer—it provides an alternative approach to fuzzy matching that trades accuracy for speed. The benchmark comparison below shows HybridMatcher vs. full automaton as competing approaches, not as an optimization to the automaton itself.

Filter Performance

Dict SizeQuery Typed=1d=2
1,000Exact match50.3 µs383 µs
1,000Typo7.7 µs7.3 µs
1,000Distant174 ns229 ns
10,000Exact match68.3 µs365 µs
10,000Typo8.2 µs8.4 µs
10,000Distant161 ns218 ns
50,000Exact match67.7 µs351 µs
50,000Typo10.6 µs8.3 µs
50,000Distant185 ns176 ns

Comparison: Hybrid Filter vs Full Levenshtein Automaton

Dictionary SizeHybrid FilterFull AutomatonRatio
1,000 terms7.9 µs24.6 µs3.1x faster
10,000 terms8.2 µs25.6 µs3.1x faster

Key Findings

  • Hybrid is ~3x faster but uses approximate filtering (may miss some matches)
  • Automaton is exact and finds all matches within distance threshold
  • Constant time for distant/non-matching queries (~175-230 ns)
  • Sub-linear scaling with dictionary size due to efficient n-gram lookup

When to Use Each

ApproachUse When
HybridMatcherSpeed matters more than completeness; interactive UIs; autocomplete
Full AutomatonNeed all matches; exact distance guarantees; precision-critical

5. Priority Query Iterator (Integrated Automaton Improvement)

The PriorityQueryIterator uses A*-style search to return results in order of increasing edit distance.

Integrated: This is the only module in this document that is fully integrated into the automata pipeline. It uses the same State, Position, Intersection, and transition_state_pooled() functions as the standard OrderedQueryIterator—the only difference is the search strategy (A* with priority queue vs. BFS with distance buckets).

First Result Retrieval

Dict SizeQuery TypePriorityOrderedWinner
1,000Exact23.7 µs13.8 µsOrdered
1,000Typo18.2 µs34.7 µsPriority (1.9x)
1,000Distant16.9 µs38.8 µsPriority (2.3x)
10,000Exact19.1 µs12.7 µsOrdered
10,000Typo20.4 µs38.2 µsPriority (1.9x)
10,000Distant13.5 µs53.5 µsPriority (4.0x)

First-K Results

KPriorityOrderedWinner
128.1 µs16.3 µsOrdered
576.3 µs101.3 µsPriority (1.3x)
10150.9 µs76.2 µsOrdered
25153.3 µs74.0 µsOrdered

Exhaustive Iteration

Dict SizePriorityOrdered
1,000132 µs78.5 µs
5,000104 µs78.8 µs

Key Findings

  • Priority excels for typos and distant queries (1.9-4.0x faster for first result)
  • Ordered excels for exact matches (overhead of priority queue not worthwhile)
  • Priority better for first-K when K ≤ 5; Ordered better for larger K
  • Ordered always faster for exhaustive iteration (no priority queue overhead)

Recommendation

Use CaseIterator Choice
Find closest match to typoPriority
Top-5 suggestionsPriority
Find exact matchOrdered
Get all resultsOrdered
Top-10+ resultsOrdered

6. Articulatory Phonetic Distance (Standalone Utility)

Computes edit distance with phonetically-informed substitution costs based on articulatory features.

Note: This is a standalone utility exported via liblevenshtein::phonetic::feature_distance. While an ArticulatoryCosts structure exists with a substitution_cost(from, to) method for character-pair costs, the core Levenshtein automata do NOT use these during traversal—they use fixed operation costs. Use this module for standalone phonetic edit distance computation independent of the automata.

Single Character Distance

Character PairTypeTime
p ↔ p (identical)Same8.9 ns
p ↔ b (voicing only)Free sub16.7 ns
p ↔ t (adjacent place)Low cost14.7 ns
p ↔ k (distant place)Medium cost14.9 ns
p ↔ s (manner change)Higher cost13.9 ns
a ↔ i (vowels)Vowel distance33.9 ns

Full Edit Distance Comparison

String PairStandardArticulatoryOverhead
pat → bat (voicing)82 ns712 ns8.7x
pattern → battern109 ns4.6 µs42x
information → confirmation126 ns12.2 µs97x
kitten → sitting101 ns3.9 µs39x

Throughput

ModeThroughput
Standard batch10.2 M elem/s
Articulatory batch250 K elem/s

Key Findings

  • 40-100x overhead compared to standard edit distance
  • Meaningful phonetic costs - voicing changes (p↔b) are "free" substitutions
  • Feature lookup overhead is minimal (IPA vs ASCII chars similar performance)
  • Best for: Spell-checking where phonetic similarity matters (homophones, accent variations)

Recommendation

Use articulatory distance when:

  • Phonetic accuracy is more important than speed
  • Processing user-facing spell-check suggestions
  • Matching names with pronunciation variations

Use standard distance when:

  • Speed is critical
  • Character-level accuracy is sufficient
  • Batch processing large datasets

7. Product Automaton with Articulatory Costs (Integrated)

The ProductAutomatonChar now supports articulatory-weighted substitution costs via with_articulatory_costs().

Integrated: This module extends the phonetic product automaton (NFA × Levenshtein) by using ArticulatoryCosts.substitution_cost(from, to) for character-specific substitution weights. Phonetically similar character pairs (voicing, adjacent place) incur lower costs than dissimilar pairs.

Transition Overhead

Transition TypeFixed CostsArticulatory CostsOverhead
Match (no substitution)~10 µs~10 µs1.0x
Substitution (similar: p→b)~3.4 µs~3.3 µs~same
Substitution (different: p→k)~3.4 µs~3.5 µs1.03x

Full Query Performance

QueryFixed CostsArticulatory CostsWinner
Exact match3.4 µs3.3 µsArticulatory
One sub (similar)10.2 µs10.3 µs~same
One sub (different)10.2 µs10.2 µs~same
No match (distant)3.4 µs3.3 µsArticulatory

Substitution Cost Lookup

Character PairTypeLookup Time
Identical (p→p)Free432 ns
Voicing pair (p→b)Low cost475 ns
Adjacent place (p→t)Medium cost483 ns
Distant place (p→k)High cost482 ns
Different manner (p→s)High cost477 ns
Vowel (a)N/A (consonant pattern)442 ns
Non-IPA (x)Fallback496 ns

Key Findings

  • Minimal transition overhead (~1.6-1.8x for substitution lookup)
  • Comparable or faster full query due to better pruning (high-cost paths rejected earlier)
  • Substitution cost lookup is fast (~430-500 ns regardless of character type)
  • No regression for exact match or no-match queries

Recommendation

Use CaseConfiguration
Phonetic spelling correctionArticulatory costs (default weight 0.6)
Name matching across languagesArticulatory costs (weight 0.8-1.0)
Keyboard typo correctionFixed costs (typos have no phonetic relationship)
Maximum throughputFixed costs (avoid lookup overhead)

See docs/guides/articulatory-distance.md for detailed usage.


Summary

ModuleIntegrationBest Use CaseSpeedup/Overhead
Myers' Bit-ParallelStandalonePairwise distance (reference impl)5-15x vs DP
N-gram IndexStandaloneApplication-level pre-filteringSub-ms per query
Jaro-WinklerStandaloneName matching, similarity screening30-138 MiB/s
Hybrid MatcherStandaloneAlternative to automaton for autocomplete3.1x vs full automaton
Priority IteratorIntegratedFirst closest match for typos2-4x for typos
Articulatory PairwiseStandalonePhonetic pairwise distance40-100x overhead
Product Automaton (Articulatory)IntegratedPhonetic fuzzy regex matching~same (better pruning)

Architectural Context

The modules in this document fall into two categories:

Integrated (Core Pipeline)

  • PriorityQueryIterator: Uses the same State, Position, Intersection, and transition_state_pooled() as the standard OrderedQueryIterator. The only difference is search strategy: A* with priority queue vs BFS with distance buckets. This genuinely improves first-K result retrieval for typos and distant queries.
  • ProductAutomatonChar with ArticulatoryCosts: The product automaton (NFA × Levenshtein) now uses ArticulatoryCosts.substitution_cost(from, to) for phonetically-informed substitution costs. This integrates articulatory distance into the automaton traversal for residual errors not covered by explicit NFA rules.

Standalone Utilities

  • Myers' Bit-Parallel: Pairwise distance algorithm; automata don't compute distances during traversal
  • Filter Module: Application developers can use N-gram, Jaro-Winkler, or HybridMatcher independently for pre-filtering or as alternatives to automaton traversal
  • Articulatory Pairwise Distance: The articulatory_edit_distance() function computes full edit distance with phonetic costs; this standalone function is separate from the automaton-integrated ProductAutomatonChar

Automata vs Reference Implementation

The core value of liblevenshtein is the Levenshtein automata approach, which finds all dictionary matches within edit distance d in a single traversal—without computing pairwise distances. This is fundamentally different from algorithms like Myers' that compute the distance between two specific strings.

ApproachUse CaseComplexity
Levenshtein AutomataFind all matches in dictionaryO(|query| × |states|), independent of dict size
Myers' Bit-ParallelDistance between two stringsO(⌈m/64⌉ × n)

The standalone utilities (Myers, Filter module, Articulatory) can be composed by applications as needed but don't change how the automaton traverses the dictionary. Only PriorityQueryIterator modifies the core query pipeline.

Running Benchmarks

# Myers distance benchmarks
cargo bench --bench distance_benchmarks -- myers

# Pre-filtering benchmarks
cargo bench --bench filter_benchmarks

# Priority query benchmarks
cargo bench --bench priority_query_benchmarks

# Articulatory benchmarks (requires phonetic-rules)
cargo bench --bench articulatory_benchmarks --features phonetic-rules,embedded-rules

Benchmarks run on Intel Xeon E5-2699 v3 @ 2.30GHz, 252GB DDR4-2133 ECC, Arch Linux 6.18.3, Rust 1.84.0 Criterion 0.5 with default sample size (100) except where noted

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