Status: ❌ REJECTED
Date: 2025-11-12
Branch: feature/h2-bitmap-substitution (to be removed)
Baseline Commit: e5a32a0
Statement: For byte-level (ASCII) substitutions, a 128×128 bit matrix (2KB) would provide O(1) lookup with excellent cache locality, outperforming hash-based approaches for small-to-medium sized sets.
Rationale:
Expected Impact: 3-10% improvement for ASCII contains() calls
Data Structure:
pub struct SubstitutionSetBitmap {
/// 128×128 bit matrix stored as bytes.
/// Index calculation: bitmap[a * 16 + b / 8] & (1 << (b % 8))
/// where 16 = 128 / 8 (bytes per row)
bitmap: [u8; 2048], // 128 * 128 / 8 = 2048 bytes
}
Memory Layout:
[a][b] indicates if substitution a → b is alloweda occupies bytes a*16 through a*16+15Core Operations:
contains):#[inline]
pub fn contains(&self, dict_char: u8, query_char: u8) -> bool {
if dict_char >= 128 || query_char >= 128 {
return false;
}
let byte_index = (dict_char as usize) * 16 + (query_char as usize) / 8;
let bit_offset = query_char % 8;
(self.bitmap[byte_index] & (1 << bit_offset)) != 0
}
allow_byte):#[inline]
pub fn allow_byte(&mut self, a: u8, b: u8) {
if a >= 128 || b >= 128 {
return;
}
let byte_index = (a as usize) * 16 + (b as usize) / 8;
let bit_offset = b % 8;
self.bitmap[byte_index] |= 1 << bit_offset;
}
src/transducer/substitution_set_bitmap.rs (CREATED - 320 lines)
src/transducer/mod.rs (MODIFIED)
pub mod substitution_set_bitmap;benches/substitution_set_microbench.rs (MODIFIED - Added 142 lines)
bench_bitmap_vs_hash_single() - Single lookup comparisonbench_bitmap_vs_hash_batch() - Batch lookup with varying sizesbench_bitmap_vs_hash_presets() - Preset initialization comparisonRUSTFLAGS="-C target-cpu=native" taskset -c 0 \
cargo bench --bench substitution_set_microbench --features rand \
-- "bitmap_vs_hash"
Hardware:
Compiler: rustc with -C target-cpu=native
| Operation | Hash (FxHashSet) | Bitmap | Improvement | Speedup |
|---|---|---|---|---|
| Hit | 5.18 ns | 2.30 ns | 55.5% | 2.25× |
| Miss | 5.32 ns | 2.25 ns | 57.7% | 2.37× |
Analysis:
| Set Size | Hash (FxHashSet) | Bitmap | Improvement | Speedup |
|---|---|---|---|---|
| 10 pairs | 420.6 ns | 176.9 ns | 58.0% | 2.37× |
| 50 pairs | 438.5 ns | 176.3 ns | 59.8% | 2.49× |
| 100 pairs | 414.8 ns | 172.5 ns | 58.5% | 2.41× |
Throughput (batch/100):
Analysis:
| Preset | Pairs | Hash (FxHashSet) | Bitmap | Slowdown |
|---|---|---|---|---|
| phonetic_basic | 14 | 177.7 ns | 2,243.5 ns | 12.6× |
| keyboard_qwerty | 68 | 563.7 ns | 2,303.9 ns | 4.1× |
| leet_speak | 22 | 223.9 ns | 2,151.2 ns | 9.6× |
Analysis:
Lookup Performance Exceeded Expectations
Cache Behavior is Excellent
Consistent Performance
Initialization Overhead is Catastrophic
Memory Overhead for Small Sets
No Benefit from Const Array Optimization
Critical Flaw: Initialization overhead (4-13×) outweighs lookup performance benefits (2.4×)
Why This Matters:
Preset initialization happens at program startup
Lookup performance doesn't justify cost
Memory overhead for small sets
Cannot leverage const array optimization (H1)
When would bitmap be worthwhile?
Assuming:
Break-even point: 2,000ns / 2.9ns ≈ 690 lookups
Real-world scenarios:
Conclusion: Only large queries (distance ≥3) would benefit, but:
Idea: Only initialize bitmap on first lookup, not during construction.
Why Rejected:
Initialization cost matters
Memory footprint matters for small sets
Const arrays are powerful (H1 confirmed again)
Measurement before optimization
# Build optimized
RUSTFLAGS="-C target-cpu=native" cargo build --release --features rand
# Run benchmarks
RUSTFLAGS="-C target-cpu=native" taskset -c 0 \
cargo bench --bench substitution_set_microbench --features rand \
-- "bitmap_vs_hash" 2>&1 | tee /tmp/bitmap_vs_hash_results.txt
rustc --version (to be recorded)taskset -c 0)feature/h2-bitmap-substitution (experimental, to be deleted)src/transducer/substitution_set_bitmap.rsbenches/substitution_set_microbench.rssrc/transducer/mod.rs00-experiment-log.md with H2 resultsConclusion: While bitmap lookup performance is excellent (2.4× faster), the 4-13× initialization overhead makes this optimization unsuitable for production use. The break-even point (~700 lookups) exceeds typical query patterns, and the memory overhead penalizes small substitution sets.
Decision: ❌ REJECT - Initialization cost outweighs lookup benefits.
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 |