Date: 2025-11-30
Optimization: Conditional Position Skipping for Phonetic Rewrite Rules
Formal Verification: docs/verification/phonetic/position_skipping_proof.v
Context::Finaltaskset -c 0)RUSTFLAGS="-C target-cpu=native"phonetic_rules() - computationally expensive rule set (no Context::Final)phonetic_position_skipping_comparison| Input Size | Standard (apply_rules_seq) | Optimized (apply_rules_seq_opt) | Speedup |
|---|---|---|---|
| 10 chars | 156.47 ns | 164.85 ns | 0.95× (5% overhead) |
| 25 chars | 730.79 µs | 178.92 µs | 4.08× |
| 50 chars | 2.43 ms | 1.24 ms | 1.96× |
| 100 chars | 8.72 ms | 1.31 ms | 6.65× |
| 200 chars | 29.29 ms | 2.25 ms | 13.0× |
| 500 chars | 173.54 ms | 6.51 ms | 26.6× |
| Implementation | Time Complexity | Space Complexity |
|---|---|---|
| Standard | O(n² × r) | O(n) |
| Optimized | O(n × r) | O(n) |
Where:
n = input string lengthr = number of rulesThe optimization eliminates redundant re-scanning from position 0 after each rule application.
H₁ CONFIRMED: Position skipping provides statistically significant performance improvement for inputs ≥25 characters when rules don't use Context::Final.
auto_opt behavior: Correctly detects safe rules and applies optimizationThe optimization is proven correct in Coq/Rocq:
position_skipping_conditionally_safe (position_skipping_proof.v:1850)¬ has_final_context(rules) (no rules use Context::Final)apply_rules_seq_opt(rules, s) = apply_rules_seq(rules, s)Counter-example for unsafe case (position_skipping_proof.v:3424-3444):
[a,b,c] (length 3), position 2 is NOT final[a,b] (length 2), position 2 IS now finalFinal context might match at a position previously skipped// Byte-level (u8)
pub fn apply_rules_seq(rules: &[RewriteRule], s: &[Phone], fuel: usize) -> Option<Vec<Phone>>;
pub fn apply_rules_seq_optimized(rules: &[RewriteRule], s: &[Phone], fuel: usize) -> Option<Vec<Phone>>;
#[deprecated] pub fn apply_rules_seq_opt(...); // Now just calls apply_rules_seq
pub fn has_position_dependent_rules(rules: &[RewriteRule]) -> bool;
// Character-level (char)
pub fn apply_rules_seq_char(rules: &[RewriteRuleChar], s: &[PhoneChar], fuel: usize) -> Option<Vec<PhoneChar>>;
pub fn apply_rules_seq_optimized_char(rules: &[RewriteRuleChar], s: &[PhoneChar], fuel: usize) -> Option<Vec<PhoneChar>>;
#[deprecated] pub fn apply_rules_seq_opt_char(...); // Now just calls apply_rules_seq_char
pub fn has_position_dependent_rules_char(rules: &[RewriteRuleChar]) -> bool;
| Function | Behavior | Recommended Use |
|---|---|---|
apply_rules_seq | Standard implementation - starts search from position 0 after each rule application | Default - all typical workloads |
apply_rules_seq_optimized | Position skipping - starts search from last match position (caller must verify no Context::Final rules) | Opt-in - long repetitive strings (100+ chars) |
apply_rules_seq_opt | Deprecated - now delegates to apply_rules_seq | Migrate to apply_rules_seq |
has_position_dependent_rules | Returns true if any rule uses Context::Final | Safety check before using optimized version |
src/phonetic/types.rs: Added Context::is_position_dependent() methodsrc/phonetic/application.rs: Added optimized functions and safety checksrc/phonetic/mod.rs: Exported new API functionsbenches/phonetic_position_skip_benchmark.rs: Comparison benchmark suiteDate: 2025-11-30 Purpose: Validate optimization effectiveness on real-world English words
/usr/share/dict/words (system dictionary)phonetic_rules() (same as synthetic benchmarks)| Category | Avg Length | Standard (µs) | Optimized (µs) | Speedup |
|---|---|---|---|---|
| Short (3-5) | 4 chars | 9.04 | 9.13 | 0.99× (1% overhead) |
| Medium (6-10) | 7 chars | 14.53 | 14.92 | 0.97× (3% overhead) |
| Long (11-15) | 11 chars | 10.19 | 11.97 | 0.85× (15% overhead) |
| Very Long (16+) | 16 chars | 7.21 | 7.43 | 0.97× (3% overhead) |
| Phrase Length | Standard (ns) | Optimized (ns) | Speedup |
|---|---|---|---|
| 20 chars | 292.60 | 302.28 | 0.97× (3% overhead) |
| 25 chars | 332.11 | 376.43 | 0.88× (13% overhead) |
| 30 chars | 463.31 | 487.07 | 0.95× (5% overhead) |
| 40 chars | 569.55 | 591.15 | 0.96× (4% overhead) |
| 50 chars | 577.57 | 616.48 | 0.94× (6% overhead) |
H₂ CONFIRMED: Position skipping provides NO benefit for real English words.
The optimized version is consistently slower across all categories:
| Factor | Synthetic Inputs | Real English Words |
|---|---|---|
| Character distribution | Repetitive ("a".repeat(500)) | Varied (natural language) |
| Rule match density | High (many patterns match) | Low (few patterns match) |
| Position skip benefit | Large (many positions skipped) | Minimal (few matches to skip past) |
| Tracking overhead | Amortized by large gains | Dominates (no gains to offset) |
The position skipping optimization's O(n² × r) → O(n × r) improvement only manifests when:
For natural language text:
Based on these findings, apply_rules_seq_optimized should:
The position skipping optimization provides substantial performance improvements (up to 26.6×) for synthetic repetitive strings while maintaining formal correctness through Coq proofs.
IMPORTANT: For real English dictionary words and phrases (3-50 chars), the optimization provides NO benefit and introduces 1-15% overhead. The apply_rules_seq_optimized function should only be used for:
For spell-checking individual words, use the standard apply_rules_seq function.
Based on the benchmark results, the following API changes were made in v0.8.0:
| Function | Status | When to Use |
|---|---|---|
apply_rules_seq | Recommended | Default choice for all typical workloads |
apply_rules_seq_optimized | Opt-in | Long repetitive strings (100+ chars) with high rule match density |
apply_rules_seq_opt | Deprecated | Migrate to apply_rules_seq |
Before (v0.7.x):
// Auto-detected optimization (could cause overhead for typical words)
let result = apply_rules_seq_opt(&rules, &input, fuel);
After (v0.8.0+):
// Standard implementation (recommended for most use cases)
let result = apply_rules_seq(&rules, &input, fuel);
// OR: Explicit opt-in to optimization (for long repetitive strings only)
if !has_position_dependent_rules(&rules) && input.len() > 100 {
let result = apply_rules_seq_optimized(&rules, &input, fuel);
}
Is input length > 100 chars?
├── NO → Use apply_rules_seq
└── YES → Does input have repetitive patterns with high rule match density?
├── NO → Use apply_rules_seq
└── YES → Does rule set use Context::Final?
├── YES → Use apply_rules_seq (optimization unsafe)
└── NO → Use apply_rules_seq_optimized (up to 26.6× speedup)
The position skipping optimization was originally auto-applied via apply_rules_seq_opt when deemed safe. However, benchmarks revealed:
The auto-detection was based on rule safety (Context::Final), but this missed the crucial factor: rule match density. Natural language text has low match density, making the optimization counterproductive.
The new API design makes the standard implementation the default and requires explicit opt-in for the optimization, ensuring users only pay the tracking cost when they expect to benefit from it.
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 |