Goal: Achieve MLP (Memory-Level Parallelism) by interleaving multiple independent fuzzy queries to overlap DRAM latencies.
Hypothesis: Interleaving N independent queries will achieve 2-4x throughput improvement for batch workloads.
Dictionary: data/english_words.txt (123,985 English words)
2026-01-05
Round-robin processing of multiple queries overlaps DRAM wait times, improving throughput.
BatchQueryProcessor struct with interleaved BFS loopsrc/transducer/batch.rssrc/transducer/mod.rsDictionary: data/english_words.txt (123,985 words)
Algorithm: Standard Levenshtein
Max distance: 2
Queries: Evenly sampled from dictionary
| Queries | Sequential | Batch | Throughput (seq) | Throughput (batch) | Regression |
|---|---|---|---|---|---|
| 10 | 17.2ms | 18.1ms | 581 elem/s | 553 elem/s | +5% |
| 50 | 94.3ms | 100.8ms | 530 elem/s | 496 elem/s | +7% |
| 100 | 186.6ms | 229.9ms | 536 elem/s | 435 elem/s | +23% |
| 500 | 922ms | 1.38s | 542 elem/s | 362 elem/s | +50% |
| 1000 | 1.82s | ~2.9s | 550 elem/s | ~350 elem/s | +60% |
The hypothesis is REJECTED. Batch processing is consistently slower than sequential processing, with the overhead increasing as batch size grows.
Root cause analysis:
Dictionary fits in L3 cache: The 124K word dictionary likely fits entirely in L3 cache (~32-64MB on modern CPUs). This means DRAM latency is NOT the bottleneck - cache hits dominate.
Overhead of interleaving: The round-robin scheduling introduces significant overhead:
Already optimized baseline: The existing sequential QueryIterator is highly optimized with:
MLP hypothesis invalid for this workload: Cimple-style MLP works when:
None of these conditions apply here - the workload is CPU-bound with high cache hit rates.
Profile before optimizing: Should have profiled to confirm DRAM latency was a bottleneck before implementing MLP.
Cache effects dominate: For in-memory data structures that fit in cache, interleaving doesn't help - it actually hurts by polluting the cache and disrupting prefetching.
MLP requires memory-bound workloads: Cimple-style interleaving only helps when waiting for slow I/O or uncached memory accesses.
Simple is often faster: The straightforward sequential approach benefits from:
Batch APIs can still be useful for ergonomics (single function call for multiple queries) even if not for performance. However, the implementation should just loop sequentially internally.
Larger batch sizes provide more MLP opportunity but increase memory pressure.
Test batch sizes: 4, 8, 16, 32
(To be filled)
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 |