|
| 1 | +# Design Document |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The CRC parameters caching system will add a thread-safe, memory-efficient cache to the `CrcParams::new()` method. The cache will store pre-computed folding keys indexed by the input parameters, eliminating redundant key generation for identical parameter sets. The design prioritizes performance, thread safety, and minimal memory overhead while maintaining complete API compatibility. |
| 6 | + |
| 7 | +## Architecture |
| 8 | + |
| 9 | +### Cache Structure |
| 10 | + |
| 11 | +The caching system will use a global, thread-safe cache implemented with: |
| 12 | + |
| 13 | +- **Cache Storage**: `std::collections::HashMap<CrcParamsCacheKey, [u64; 23]>` |
| 14 | +- **Thread Safety**: `std::sync::RwLock` for concurrent read access with exclusive write access |
| 15 | +- **Cache Key**: Custom struct containing all parameters that affect key generation |
| 16 | +- **Lazy Initialization**: `std::sync::OnceLock` to initialize the cache on first use |
| 17 | + |
| 18 | +### Cache Key Design |
| 19 | + |
| 20 | +```rust |
| 21 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 22 | +struct CrcParamsCacheKey { |
| 23 | + width: u8, |
| 24 | + poly: u64, |
| 25 | + reflected: bool, |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +The cache key includes only the parameters that directly affect key generation (`width`, `poly`, `reflected`), excluding parameters like `name`, `init`, `xorout`, and `check` which don't influence the mathematical key computation. |
| 30 | + |
| 31 | +### Cache Access Pattern |
| 32 | + |
| 33 | +1. **Cache Hit Path**: Read lock → HashMap lookup → Return cached keys |
| 34 | +2. **Cache Miss Path**: Read lock → Cache miss → Generate keys → Write lock → Store in cache → Return keys |
| 35 | +3. **Concurrent Access**: Multiple readers can access simultaneously; writers get exclusive access |
| 36 | + |
| 37 | +## Components and Interfaces |
| 38 | + |
| 39 | +### Core Components |
| 40 | + |
| 41 | +#### 1. Cache Module (`src/cache.rs`) |
| 42 | + |
| 43 | +```rust |
| 44 | +use std::collections::HashMap; |
| 45 | +use std::sync::{OnceLock, RwLock}; |
| 46 | + |
| 47 | +static CACHE: OnceLock<RwLock<HashMap<CrcParamsCacheKey, [u64; 23]>>> = OnceLock::new(); |
| 48 | + |
| 49 | +pub fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] |
| 50 | +pub fn clear_cache() // For testing and memory management |
| 51 | +``` |
| 52 | + |
| 53 | +#### 2. Cache Key Structure |
| 54 | + |
| 55 | +```rust |
| 56 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 57 | +struct CrcParamsCacheKey { |
| 58 | + width: u8, |
| 59 | + poly: u64, |
| 60 | + reflected: bool, |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +#### 3. Modified CrcParams Implementation |
| 65 | + |
| 66 | +The existing `CrcParams::new()` method will be updated to use the cache: |
| 67 | + |
| 68 | +```rust |
| 69 | +impl CrcParams { |
| 70 | + pub fn new( |
| 71 | + name: &'static str, |
| 72 | + width: u8, |
| 73 | + poly: u64, |
| 74 | + init: u64, |
| 75 | + reflected: bool, |
| 76 | + xorout: u64, |
| 77 | + check: u64, |
| 78 | + ) -> Self { |
| 79 | + let keys = cache::get_or_generate_keys(width, poly, reflected); |
| 80 | + |
| 81 | + let algorithm = match width { |
| 82 | + 32 => CrcAlgorithm::Crc32Custom, |
| 83 | + 64 => CrcAlgorithm::Crc64Custom, |
| 84 | + _ => panic!("Unsupported width: {}", width), |
| 85 | + }; |
| 86 | + |
| 87 | + Self { |
| 88 | + algorithm, |
| 89 | + name, |
| 90 | + width, |
| 91 | + poly, |
| 92 | + init, |
| 93 | + refin: reflected, |
| 94 | + refout: reflected, |
| 95 | + xorout, |
| 96 | + check, |
| 97 | + keys, |
| 98 | + } |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +### Interface Design |
| 104 | + |
| 105 | +#### Public Interface |
| 106 | +- No changes to existing public APIs |
| 107 | +- `CrcParams::new()` maintains identical signature and behavior |
| 108 | +- Cache operations are completely internal |
| 109 | + |
| 110 | +#### Internal Interface |
| 111 | +- `cache::get_or_generate_keys()` - Primary cache interface |
| 112 | +- `cache::clear_cache()` - For testing and memory management |
| 113 | +- Cache key creation and hashing handled internally |
| 114 | + |
| 115 | +## Data Models |
| 116 | + |
| 117 | +### Cache Key Model |
| 118 | +```rust |
| 119 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 120 | +struct CrcParamsCacheKey { |
| 121 | + width: u8, // CRC width (32 or 64) |
| 122 | + poly: u64, // Polynomial value |
| 123 | + reflected: bool, // Reflection mode |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +### Cache Storage Model |
| 128 | +```rust |
| 129 | +type CacheStorage = HashMap<CrcParamsCacheKey, [u64; 23]>; |
| 130 | +type ThreadSafeCache = RwLock<CacheStorage>; |
| 131 | +``` |
| 132 | + |
| 133 | +### Memory Layout Considerations |
| 134 | +- Cache keys: ~17 bytes per entry (8 + 8 + 1 bytes + HashMap overhead) |
| 135 | +- Cache values: 184 bytes per entry (23 × 8 bytes) |
| 136 | +- Total per entry: ~201 bytes + HashMap overhead |
| 137 | +- Expected usage: 1-10 unique parameter sets in typical applications (single parameter set most common) |
| 138 | + |
| 139 | +## Error Handling |
| 140 | + |
| 141 | +### Cache Access Errors |
| 142 | +- **RwLock Poisoning**: If a thread panics while holding the write lock, subsequent accesses will fall back to direct key generation |
| 143 | +- **Memory Allocation**: HashMap growth failures will be handled by Rust's standard allocation error handling |
| 144 | + |
| 145 | +### Fallback Strategy |
| 146 | +```rust |
| 147 | +fn get_or_generate_keys(width: u8, poly: u64, reflected: bool) -> [u64; 23] { |
| 148 | + let cache_key = CrcParamsCacheKey { width, poly, reflected }; |
| 149 | + |
| 150 | + // Try cache read first |
| 151 | + if let Ok(cache) = get_cache().read() { |
| 152 | + if let Some(keys) = cache.get(&cache_key) { |
| 153 | + return *keys; |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + // Generate keys outside of write lock to minimize lock hold time |
| 158 | + let keys = generate::keys(width, poly, reflected); |
| 159 | + |
| 160 | + // Try to cache the result (best effort) |
| 161 | + if let Ok(mut cache) = get_cache().write() { |
| 162 | + cache.insert(cache_key, keys); |
| 163 | + } |
| 164 | + |
| 165 | + keys |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +### Error Recovery |
| 170 | +- Lock poisoning: Continue with direct key generation |
| 171 | +- Memory pressure: Cache operations become no-ops, functionality preserved |
| 172 | +- Hash collisions: Handled by HashMap implementation |
| 173 | + |
| 174 | +## Testing Strategy |
| 175 | + |
| 176 | +### Unit Tests |
| 177 | +1. **Cache Functionality** |
| 178 | + - Verify cache hits return identical keys |
| 179 | + - Verify cache misses generate and store keys |
| 180 | + - Test cache key equality and hashing |
| 181 | + |
| 182 | +2. **Thread Safety** |
| 183 | + - Concurrent read access tests |
| 184 | + - Read-write contention tests |
| 185 | + - Cache consistency under concurrent access |
| 186 | + |
| 187 | +3. **Performance Tests** |
| 188 | + - Benchmark cache hit vs. miss performance |
| 189 | + - Memory usage validation |
| 190 | + - Comparison with uncached implementation |
| 191 | + |
| 192 | +4. **Edge Cases** |
| 193 | + - Empty cache behavior |
| 194 | + - Cache with single entry |
| 195 | + - Maximum realistic cache size |
| 196 | + - Lock poisoning recovery |
| 197 | + |
| 198 | +### Integration Tests |
| 199 | +1. **API Compatibility** |
| 200 | + - Existing CrcParams::new() behavior unchanged |
| 201 | + - All existing tests continue to pass |
| 202 | + - Identical results for cached vs. uncached keys |
| 203 | + |
| 204 | +2. **Real-world Usage Patterns** |
| 205 | + - Multiple CrcParams instances with same parameters |
| 206 | + - Mixed usage with different parameters |
| 207 | + - Long-running application simulation |
| 208 | + |
| 209 | +### Performance Benchmarks |
| 210 | +1. **Cache Hit Performance**: Measure lookup time vs. key generation time |
| 211 | +2. **Cache Miss Performance**: Measure overhead of cache check + generation |
| 212 | +3. **Memory Usage**: Track cache memory consumption over time |
| 213 | +4. **Concurrent Access**: Measure performance under thread contention |
| 214 | + |
| 215 | +## Implementation Phases |
| 216 | + |
| 217 | +### Phase 1: Core Cache Implementation |
| 218 | +- Create cache module with basic HashMap storage |
| 219 | +- Implement thread-safe access with RwLock |
| 220 | +- Add cache key structure and hashing |
| 221 | + |
| 222 | +### Phase 2: Integration |
| 223 | +- Modify CrcParams::new() to use cache |
| 224 | +- Add fallback error handling |
| 225 | +- Ensure API compatibility |
| 226 | + |
| 227 | +### Phase 3: Testing and Optimization |
| 228 | +- Comprehensive test suite |
| 229 | +- Performance benchmarking |
| 230 | +- Memory usage optimization |
| 231 | +- Documentation updates |
| 232 | + |
| 233 | +## Performance Considerations |
| 234 | + |
| 235 | +### Cache Hit Performance |
| 236 | +- Expected improvement: 50-100x faster than key generation |
| 237 | +- RwLock read access: ~10-20ns overhead |
| 238 | +- HashMap lookup: O(1) average case, ~50-100ns |
| 239 | + |
| 240 | +### Cache Miss Performance |
| 241 | +- Additional overhead: ~100-200ns for cache check |
| 242 | +- Write lock acquisition: ~50-100ns |
| 243 | +- HashMap insertion: O(1) average case |
| 244 | + |
| 245 | +### Memory Efficiency |
| 246 | +- Cache overhead per entry: ~201 bytes |
| 247 | +- Expected cache size: 200 bytes - 2KB for typical applications |
| 248 | +- Memory growth: Linear with unique parameter combinations |
| 249 | + |
| 250 | +### Thread Contention |
| 251 | +- Read-heavy workload: Excellent scalability |
| 252 | +- Write contention: Minimal impact (writes are rare after warmup) |
| 253 | +- Lock-free reads: Multiple threads can read simultaneously |
| 254 | + |
| 255 | +## Security Considerations |
| 256 | + |
| 257 | +### Memory Safety |
| 258 | +- All cache operations use safe Rust constructs |
| 259 | +- No unsafe code in cache implementation |
| 260 | +- HashMap provides memory safety guarantees |
| 261 | + |
| 262 | +### Thread Safety |
| 263 | +- RwLock prevents data races |
| 264 | +- Cache key immutability prevents modification after creation |
| 265 | +- Atomic operations for cache initialization |
| 266 | + |
| 267 | +### Resource Management |
| 268 | +- Cache growth is bounded by unique parameter combinations |
| 269 | +- No automatic eviction policy (acceptable for typical usage) |
| 270 | +- Manual cache clearing available for memory management |
0 commit comments