Date: 2025-11-03 Status: Implementation Halted - Trade-offs Unfavorable
RCU (Read-Copy-Update) pattern using arc-swap could eliminate read locks entirely, providing 25-35% improvement for read-heavy workloads.
Replace Arc<RwLock<DynamicDawgInner>> with Arc<ArcSwap<DynamicDawgInner>>:
Read Pattern:
// Lock-free read
let snapshot = self.inner.load(); // Returns Arc<DynamicDawgInner>
// Use snapshot - guaranteed consistent, no locks
Write Pattern:
loop {
// Load current state
let current = self.inner.load();
// Clone entire structure (EXPENSIVE!)
let mut new_state = (**current).clone();
// Modify clone
new_state.insert_node(...);
// Atomically swap if unchanged
match self.inner.compare_and_swap(¤t, Arc::new(new_state)) {
Ok(_) => break, // Success
Err(_) => continue, // Retry (concurrent modification)
}
}
arc-swap = "1.7" dependencydynamic_dawg_rcu.rs with renamed struct DynamicDawgRcuClone derive to DynamicDawgInnernew() and with_auto_minimize_threshold() to use ArcSwapinsert() method with retry loopThe Core Problem:
struct DynamicDawgInner {
nodes: Vec<DawgNode>, // Could be 1000s of nodes
// ... other fields
}
Every write operation must:
Vec<DawgNode> - O(n) where n = node countFor a 1000-node DAWG:
Write Amplification:
Memory Pressure:
Retry Loops:
Minimization Incompatibility:
minimize() and compact() already expensivePhase 1.2 (Cached Node Data) already eliminated most lock overhead for reads:
pub struct DynamicDawgNode {
dawg: Arc<RwLock<DynamicDawgInner>>,
node_idx: usize,
// CACHED - no lock needed
is_final: bool,
edges: SmallVec<[(u8, usize); 4]>,
}
Lock-free operations (Phase 1.2):
is_final() - No lock (reads cached bool)edge_count() - No lock (reads cached vec len)transition() - Minimal locking (1 lock per successful transition, using cached edges for lookup)edges() - Batch load with single lockRemaining lock overhead:
transition() - unavoidable even with RCUBased on analysis:
| Operation | RwLock (Current) | RCU (Predicted) | Winner |
|---|---|---|---|
| Query (read) | ~3-16 µs | ~2-14 µs (10-20% faster) | RCU (marginal) |
| Insert (single) | ~20 µs | ~300+ µs (15x slower!) | RwLock |
| Insert (batch 100) | ~2 ms | ~30+ ms (15x slower!) | RwLock |
| Minimize | ~6-8 µs | ~50+ µs (6-8x slower!) | RwLock |
Why queries only 10-20% faster:
Could keep RwLock for writes but optimize specific read-heavy operations:
// Cache Arc to inner for quick read-only access
struct DynamicDawg {
inner: Arc<RwLock<DynamicDawgInner>>,
read_cache: ArcSwap<ReadOnlyView>, // Periodic snapshot for queries
}
Trade-offs:
Reasons:
RCU is for specific workloads:
DynamicDawg doesn't fit:
Phase 1-2.2 was the right approach:
Instead of RCU, consider:
src/dictionary/dynamic_dawg_rcu.rs - Incomplete implementation (can be deleted)Cargo.toml - Added arc-swap dependency (can be removed)✅ Hypothesis clearly stated ✅ Implementation approach designed ✅ Trade-offs analyzed before full implementation ✅ Predicted performance calculated ✅ Decision made based on analysis (not sunk cost)
Key Insight: Sometimes the best optimization is recognizing which optimizations NOT to pursue.
Next Steps:
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 |