Skip to main content

ShardedCache

Struct ShardedCache 

Source
pub struct ShardedCache<K, V> { /* private fields */ }

Implementations§

Source§

impl<K, V> ShardedCache<K, V>
where K: Hash + Eq + Clone, V: Clone,

Source

pub fn with_capacity(total_capacity: usize, num_shards: usize) -> Self

Create a sharded cache with total_capacity distributed across num_shards shards (rounded up). Each shard gets at least 1 slot.

Examples found in repository?
examples/sample_app.rs (line 182)
177fn sharded_parallel_readers() {
178    use std::sync::Arc;
179    use std::thread;
180    use subms_block_cache::ShardedCache;
181    println!("\n== concurrent-shards: parallel query threads ==");
182    let cache: Arc<ShardedCache<u64, u64>> = Arc::new(ShardedCache::with_capacity(1024, 8));
183    println!("  {} shards", cache.num_shards());
184    for id in 0u64..256 {
185        cache.put(id, id);
186    }
187
188    let mut handles = Vec::new();
189    for _ in 0..4 {
190        let c = Arc::clone(&cache);
191        handles.push(thread::spawn(move || {
192            for id in 0u64..4000 {
193                let _ = c.get(&(id % 256));
194            }
195        }));
196    }
197    for t in 0u64..2 {
198        let c = Arc::clone(&cache);
199        handles.push(thread::spawn(move || {
200            for i in 0u64..2000 {
201                c.put(t * 10_000 + i, i);
202            }
203        }));
204    }
205    for h in handles {
206        h.join().unwrap();
207    }
208    println!("  survived concurrent load, {} pages resident", cache.len());
209    assert!(cache.len() <= 1024, "capacity holds across all shards");
210}
More examples
Hide additional examples
examples/perf_features.rs (line 223)
82fn main() -> io::Result<()> {
83    let canon = SIZES[SIZES.len() - 1];
84
85    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
86        .join("..")
87        .join(".subms")
88        .join("features")
89        .join("rust.json");
90    let existing = std::fs::read_to_string(&path).unwrap_or_default();
91    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
92    // Stamp the box these numbers came from. The bench runs wherever it is
93    // invoked, so an unstamped manifest is indistinguishable from a fleet
94    // capture; the renderer will not publish one it cannot attribute.
95    let (source, instance) = SubMsP99Source::from_env();
96    manifest.set_p99_source(source, instance.as_deref());
97
98    // ---------- base (clock-sweep): the baseline, not a feature ----------
99    // Every feature is classified against this. A variant whose lookup lands
100    // within a whisker of the base is a capability, not a latency change, and
101    // classify_feature says so rather than calling it hot-path by default.
102    // The baseline is a p50, because the sweep values are p50s. Handing
103    // classify_feature a base p99 against p50 sweep points compares two
104    // different statistics: the p50 sits below the p99 almost by construction,
105    // so every feature reads as "within 10% of base" and lands auxiliary.
106    let base_p50 = {
107        use subms_block_cache::BlockCache;
108        let mut c: BlockCache<u32, u64> = BlockCache::with_capacity(canon);
109        for k in 0..canon as u32 {
110            c.put(k, k as u64);
111        }
112        let (p50, _) = get_hit(canon, |key| c.get(&key).is_some());
113        p50
114    };
115
116    // ---------- arc: adaptive replacement, recency + frequency lists ----------
117    #[cfg(feature = "arc")]
118    {
119        use subms_block_cache::ArcCache;
120        let sweep: Vec<(usize, u64)> = SIZES
121            .iter()
122            .map(|&n| {
123                let mut c: ArcCache<u32, u64> = ArcCache::with_capacity(n);
124                for k in 0..n as u32 {
125                    c.put(k, k as u64);
126                }
127                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
128                (n, p50)
129            })
130            .collect();
131        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
132
133        let mut c: ArcCache<u32, u64> = ArcCache::with_capacity(canon);
134        for k in 0..canon as u32 {
135            c.put(k, k as u64);
136        }
137        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
138        let (_, put99) = put_evicting(canon, |key| {
139            c.put(key, key as u64);
140        });
141        let mut p99 = BTreeMap::new();
142        p99.insert("get_hit".to_string(), get99);
143        p99.insert("put".to_string(), put99);
144        manifest.set_feature("arc", cat, &p99, &reason);
145    }
146
147    // ---------- tinylfu: frequency-sketch admission ----------
148    #[cfg(feature = "tinylfu")]
149    {
150        use subms_block_cache::TinyLfuCache;
151        let sweep: Vec<(usize, u64)> = SIZES
152            .iter()
153            .map(|&n| {
154                let mut c: TinyLfuCache<u32, u64> = TinyLfuCache::with_capacity(n);
155                for k in 0..n as u32 {
156                    c.put(k, k as u64);
157                }
158                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
159                (n, p50)
160            })
161            .collect();
162        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
163
164        let mut c: TinyLfuCache<u32, u64> = TinyLfuCache::with_capacity(canon);
165        for k in 0..canon as u32 {
166            c.put(k, k as u64);
167        }
168        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
169        let (_, put99) = put_evicting(canon, |key| {
170            c.put(key, key as u64);
171        });
172        let mut p99 = BTreeMap::new();
173        p99.insert("get_hit".to_string(), get99);
174        p99.insert("put".to_string(), put99);
175        manifest.set_feature("tinylfu", cat, &p99, &reason);
176    }
177
178    // ---------- weighted: a byte budget rather than a slot count ----------
179    #[cfg(feature = "weighted")]
180    {
181        use subms_block_cache::WeightedCache;
182        // 1 byte per entry so capacity_bytes == slot capacity; eviction behaves
183        // like the base cache, which isolates the weight bookkeeping itself.
184        let sweep: Vec<(usize, u64)> = SIZES
185            .iter()
186            .map(|&n| {
187                let mut c: WeightedCache<u32, u64> =
188                    WeightedCache::with_capacity_bytes(n, |_v: &u64| 1);
189                for k in 0..n as u32 {
190                    c.put(k, k as u64);
191                }
192                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
193                (n, p50)
194            })
195            .collect();
196        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
197
198        let mut c: WeightedCache<u32, u64> =
199            WeightedCache::with_capacity_bytes(canon, |_v: &u64| 1);
200        for k in 0..canon as u32 {
201            c.put(k, k as u64);
202        }
203        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
204        let (_, put99) = put_evicting(canon, |key| {
205            let _ = c.put(key, key as u64);
206        });
207        let mut p99 = BTreeMap::new();
208        p99.insert("get_hit".to_string(), get99);
209        p99.insert("put".to_string(), put99);
210        manifest.set_feature("weighted", cat, &p99, &reason);
211    }
212
213    // ---------- concurrent-shards: measured single-threaded ----------
214    // Uncontended on purpose. This isolates the sharding INDIRECTION from the
215    // contention it exists to relieve; a multi-threaded number here would say
216    // more about the thread count than about the feature.
217    #[cfg(feature = "concurrent-shards")]
218    {
219        use subms_block_cache::ShardedCache;
220        let sweep: Vec<(usize, u64)> = SIZES
221            .iter()
222            .map(|&n| {
223                let c: ShardedCache<u32, u64> = ShardedCache::with_capacity(n, 16);
224                for k in 0..n as u32 {
225                    c.put(k, k as u64);
226                }
227                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
228                (n, p50)
229            })
230            .collect();
231        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
232
233        let c: ShardedCache<u32, u64> = ShardedCache::with_capacity(canon, 16);
234        for k in 0..canon as u32 {
235            c.put(k, k as u64);
236        }
237        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
238        let (_, put99) = put_evicting(canon, |key| {
239            c.put(key, key as u64);
240        });
241        let mut p99 = BTreeMap::new();
242        p99.insert("get_hit".to_string(), get99);
243        p99.insert("put".to_string(), put99);
244        manifest.set_feature("concurrent-shards", cat, &p99, &reason);
245    }
246
247    // ---------- metrics: hit/miss counters on the lookup path ----------
248    #[cfg(feature = "metrics")]
249    {
250        use subms_block_cache::MetricsCache;
251        let sweep: Vec<(usize, u64)> = SIZES
252            .iter()
253            .map(|&n| {
254                let mut c: MetricsCache<u32, u64> = MetricsCache::with_capacity(n);
255                for k in 0..n as u32 {
256                    c.put(k, k as u64);
257                }
258                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
259                (n, p50)
260            })
261            .collect();
262        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
263
264        let mut c: MetricsCache<u32, u64> = MetricsCache::with_capacity(canon);
265        for k in 0..canon as u32 {
266            c.put(k, k as u64);
267        }
268        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
269        let (_, put99) = put_evicting(canon, |key| {
270            c.put(key, key as u64);
271        });
272        let mut p99 = BTreeMap::new();
273        p99.insert("get_hit".to_string(), get99);
274        p99.insert("put".to_string(), put99);
275        manifest.set_feature("metrics", cat, &p99, &reason);
276    }
277
278    std::fs::create_dir_all(path.parent().unwrap())?;
279    std::fs::write(&path, manifest.to_json())?;
280    io::stdout().write_all(manifest.to_json().as_bytes())?;
281    Ok(())
282}
Source

pub fn num_shards(&self) -> usize

Examples found in repository?
examples/sample_app.rs (line 183)
177fn sharded_parallel_readers() {
178    use std::sync::Arc;
179    use std::thread;
180    use subms_block_cache::ShardedCache;
181    println!("\n== concurrent-shards: parallel query threads ==");
182    let cache: Arc<ShardedCache<u64, u64>> = Arc::new(ShardedCache::with_capacity(1024, 8));
183    println!("  {} shards", cache.num_shards());
184    for id in 0u64..256 {
185        cache.put(id, id);
186    }
187
188    let mut handles = Vec::new();
189    for _ in 0..4 {
190        let c = Arc::clone(&cache);
191        handles.push(thread::spawn(move || {
192            for id in 0u64..4000 {
193                let _ = c.get(&(id % 256));
194            }
195        }));
196    }
197    for t in 0u64..2 {
198        let c = Arc::clone(&cache);
199        handles.push(thread::spawn(move || {
200            for i in 0u64..2000 {
201                c.put(t * 10_000 + i, i);
202            }
203        }));
204    }
205    for h in handles {
206        h.join().unwrap();
207    }
208    println!("  survived concurrent load, {} pages resident", cache.len());
209    assert!(cache.len() <= 1024, "capacity holds across all shards");
210}
Source

pub fn contention_events(&self) -> u64

Source

pub fn len(&self) -> usize

Aggregate length across shards. Snapshot; under concurrent access the returned number may be slightly stale.

Examples found in repository?
examples/sample_app.rs (line 208)
177fn sharded_parallel_readers() {
178    use std::sync::Arc;
179    use std::thread;
180    use subms_block_cache::ShardedCache;
181    println!("\n== concurrent-shards: parallel query threads ==");
182    let cache: Arc<ShardedCache<u64, u64>> = Arc::new(ShardedCache::with_capacity(1024, 8));
183    println!("  {} shards", cache.num_shards());
184    for id in 0u64..256 {
185        cache.put(id, id);
186    }
187
188    let mut handles = Vec::new();
189    for _ in 0..4 {
190        let c = Arc::clone(&cache);
191        handles.push(thread::spawn(move || {
192            for id in 0u64..4000 {
193                let _ = c.get(&(id % 256));
194            }
195        }));
196    }
197    for t in 0u64..2 {
198        let c = Arc::clone(&cache);
199        handles.push(thread::spawn(move || {
200            for i in 0u64..2000 {
201                c.put(t * 10_000 + i, i);
202            }
203        }));
204    }
205    for h in handles {
206        h.join().unwrap();
207    }
208    println!("  survived concurrent load, {} pages resident", cache.len());
209    assert!(cache.len() <= 1024, "capacity holds across all shards");
210}
Source

pub fn is_empty(&self) -> bool

Source

pub fn get(&self, key: &K) -> Option<V>

Get a cloned value for key. Cloning matters because returning a borrow would extend the MutexGuard across the call site.

Examples found in repository?
examples/sample_app.rs (line 193)
177fn sharded_parallel_readers() {
178    use std::sync::Arc;
179    use std::thread;
180    use subms_block_cache::ShardedCache;
181    println!("\n== concurrent-shards: parallel query threads ==");
182    let cache: Arc<ShardedCache<u64, u64>> = Arc::new(ShardedCache::with_capacity(1024, 8));
183    println!("  {} shards", cache.num_shards());
184    for id in 0u64..256 {
185        cache.put(id, id);
186    }
187
188    let mut handles = Vec::new();
189    for _ in 0..4 {
190        let c = Arc::clone(&cache);
191        handles.push(thread::spawn(move || {
192            for id in 0u64..4000 {
193                let _ = c.get(&(id % 256));
194            }
195        }));
196    }
197    for t in 0u64..2 {
198        let c = Arc::clone(&cache);
199        handles.push(thread::spawn(move || {
200            for i in 0u64..2000 {
201                c.put(t * 10_000 + i, i);
202            }
203        }));
204    }
205    for h in handles {
206        h.join().unwrap();
207    }
208    println!("  survived concurrent load, {} pages resident", cache.len());
209    assert!(cache.len() <= 1024, "capacity holds across all shards");
210}
More examples
Hide additional examples
examples/perf_features.rs (line 227)
82fn main() -> io::Result<()> {
83    let canon = SIZES[SIZES.len() - 1];
84
85    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
86        .join("..")
87        .join(".subms")
88        .join("features")
89        .join("rust.json");
90    let existing = std::fs::read_to_string(&path).unwrap_or_default();
91    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
92    // Stamp the box these numbers came from. The bench runs wherever it is
93    // invoked, so an unstamped manifest is indistinguishable from a fleet
94    // capture; the renderer will not publish one it cannot attribute.
95    let (source, instance) = SubMsP99Source::from_env();
96    manifest.set_p99_source(source, instance.as_deref());
97
98    // ---------- base (clock-sweep): the baseline, not a feature ----------
99    // Every feature is classified against this. A variant whose lookup lands
100    // within a whisker of the base is a capability, not a latency change, and
101    // classify_feature says so rather than calling it hot-path by default.
102    // The baseline is a p50, because the sweep values are p50s. Handing
103    // classify_feature a base p99 against p50 sweep points compares two
104    // different statistics: the p50 sits below the p99 almost by construction,
105    // so every feature reads as "within 10% of base" and lands auxiliary.
106    let base_p50 = {
107        use subms_block_cache::BlockCache;
108        let mut c: BlockCache<u32, u64> = BlockCache::with_capacity(canon);
109        for k in 0..canon as u32 {
110            c.put(k, k as u64);
111        }
112        let (p50, _) = get_hit(canon, |key| c.get(&key).is_some());
113        p50
114    };
115
116    // ---------- arc: adaptive replacement, recency + frequency lists ----------
117    #[cfg(feature = "arc")]
118    {
119        use subms_block_cache::ArcCache;
120        let sweep: Vec<(usize, u64)> = SIZES
121            .iter()
122            .map(|&n| {
123                let mut c: ArcCache<u32, u64> = ArcCache::with_capacity(n);
124                for k in 0..n as u32 {
125                    c.put(k, k as u64);
126                }
127                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
128                (n, p50)
129            })
130            .collect();
131        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
132
133        let mut c: ArcCache<u32, u64> = ArcCache::with_capacity(canon);
134        for k in 0..canon as u32 {
135            c.put(k, k as u64);
136        }
137        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
138        let (_, put99) = put_evicting(canon, |key| {
139            c.put(key, key as u64);
140        });
141        let mut p99 = BTreeMap::new();
142        p99.insert("get_hit".to_string(), get99);
143        p99.insert("put".to_string(), put99);
144        manifest.set_feature("arc", cat, &p99, &reason);
145    }
146
147    // ---------- tinylfu: frequency-sketch admission ----------
148    #[cfg(feature = "tinylfu")]
149    {
150        use subms_block_cache::TinyLfuCache;
151        let sweep: Vec<(usize, u64)> = SIZES
152            .iter()
153            .map(|&n| {
154                let mut c: TinyLfuCache<u32, u64> = TinyLfuCache::with_capacity(n);
155                for k in 0..n as u32 {
156                    c.put(k, k as u64);
157                }
158                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
159                (n, p50)
160            })
161            .collect();
162        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
163
164        let mut c: TinyLfuCache<u32, u64> = TinyLfuCache::with_capacity(canon);
165        for k in 0..canon as u32 {
166            c.put(k, k as u64);
167        }
168        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
169        let (_, put99) = put_evicting(canon, |key| {
170            c.put(key, key as u64);
171        });
172        let mut p99 = BTreeMap::new();
173        p99.insert("get_hit".to_string(), get99);
174        p99.insert("put".to_string(), put99);
175        manifest.set_feature("tinylfu", cat, &p99, &reason);
176    }
177
178    // ---------- weighted: a byte budget rather than a slot count ----------
179    #[cfg(feature = "weighted")]
180    {
181        use subms_block_cache::WeightedCache;
182        // 1 byte per entry so capacity_bytes == slot capacity; eviction behaves
183        // like the base cache, which isolates the weight bookkeeping itself.
184        let sweep: Vec<(usize, u64)> = SIZES
185            .iter()
186            .map(|&n| {
187                let mut c: WeightedCache<u32, u64> =
188                    WeightedCache::with_capacity_bytes(n, |_v: &u64| 1);
189                for k in 0..n as u32 {
190                    c.put(k, k as u64);
191                }
192                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
193                (n, p50)
194            })
195            .collect();
196        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
197
198        let mut c: WeightedCache<u32, u64> =
199            WeightedCache::with_capacity_bytes(canon, |_v: &u64| 1);
200        for k in 0..canon as u32 {
201            c.put(k, k as u64);
202        }
203        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
204        let (_, put99) = put_evicting(canon, |key| {
205            let _ = c.put(key, key as u64);
206        });
207        let mut p99 = BTreeMap::new();
208        p99.insert("get_hit".to_string(), get99);
209        p99.insert("put".to_string(), put99);
210        manifest.set_feature("weighted", cat, &p99, &reason);
211    }
212
213    // ---------- concurrent-shards: measured single-threaded ----------
214    // Uncontended on purpose. This isolates the sharding INDIRECTION from the
215    // contention it exists to relieve; a multi-threaded number here would say
216    // more about the thread count than about the feature.
217    #[cfg(feature = "concurrent-shards")]
218    {
219        use subms_block_cache::ShardedCache;
220        let sweep: Vec<(usize, u64)> = SIZES
221            .iter()
222            .map(|&n| {
223                let c: ShardedCache<u32, u64> = ShardedCache::with_capacity(n, 16);
224                for k in 0..n as u32 {
225                    c.put(k, k as u64);
226                }
227                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
228                (n, p50)
229            })
230            .collect();
231        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
232
233        let c: ShardedCache<u32, u64> = ShardedCache::with_capacity(canon, 16);
234        for k in 0..canon as u32 {
235            c.put(k, k as u64);
236        }
237        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
238        let (_, put99) = put_evicting(canon, |key| {
239            c.put(key, key as u64);
240        });
241        let mut p99 = BTreeMap::new();
242        p99.insert("get_hit".to_string(), get99);
243        p99.insert("put".to_string(), put99);
244        manifest.set_feature("concurrent-shards", cat, &p99, &reason);
245    }
246
247    // ---------- metrics: hit/miss counters on the lookup path ----------
248    #[cfg(feature = "metrics")]
249    {
250        use subms_block_cache::MetricsCache;
251        let sweep: Vec<(usize, u64)> = SIZES
252            .iter()
253            .map(|&n| {
254                let mut c: MetricsCache<u32, u64> = MetricsCache::with_capacity(n);
255                for k in 0..n as u32 {
256                    c.put(k, k as u64);
257                }
258                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
259                (n, p50)
260            })
261            .collect();
262        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
263
264        let mut c: MetricsCache<u32, u64> = MetricsCache::with_capacity(canon);
265        for k in 0..canon as u32 {
266            c.put(k, k as u64);
267        }
268        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
269        let (_, put99) = put_evicting(canon, |key| {
270            c.put(key, key as u64);
271        });
272        let mut p99 = BTreeMap::new();
273        p99.insert("get_hit".to_string(), get99);
274        p99.insert("put".to_string(), put99);
275        manifest.set_feature("metrics", cat, &p99, &reason);
276    }
277
278    std::fs::create_dir_all(path.parent().unwrap())?;
279    std::fs::write(&path, manifest.to_json())?;
280    io::stdout().write_all(manifest.to_json().as_bytes())?;
281    Ok(())
282}
Source

pub fn put(&self, key: K, value: V) -> Option<(K, V)>

Insert or update. Returns the evicted entry if eviction occurred.

Examples found in repository?
examples/sample_app.rs (line 185)
177fn sharded_parallel_readers() {
178    use std::sync::Arc;
179    use std::thread;
180    use subms_block_cache::ShardedCache;
181    println!("\n== concurrent-shards: parallel query threads ==");
182    let cache: Arc<ShardedCache<u64, u64>> = Arc::new(ShardedCache::with_capacity(1024, 8));
183    println!("  {} shards", cache.num_shards());
184    for id in 0u64..256 {
185        cache.put(id, id);
186    }
187
188    let mut handles = Vec::new();
189    for _ in 0..4 {
190        let c = Arc::clone(&cache);
191        handles.push(thread::spawn(move || {
192            for id in 0u64..4000 {
193                let _ = c.get(&(id % 256));
194            }
195        }));
196    }
197    for t in 0u64..2 {
198        let c = Arc::clone(&cache);
199        handles.push(thread::spawn(move || {
200            for i in 0u64..2000 {
201                c.put(t * 10_000 + i, i);
202            }
203        }));
204    }
205    for h in handles {
206        h.join().unwrap();
207    }
208    println!("  survived concurrent load, {} pages resident", cache.len());
209    assert!(cache.len() <= 1024, "capacity holds across all shards");
210}
More examples
Hide additional examples
examples/perf_features.rs (line 225)
82fn main() -> io::Result<()> {
83    let canon = SIZES[SIZES.len() - 1];
84
85    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
86        .join("..")
87        .join(".subms")
88        .join("features")
89        .join("rust.json");
90    let existing = std::fs::read_to_string(&path).unwrap_or_default();
91    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
92    // Stamp the box these numbers came from. The bench runs wherever it is
93    // invoked, so an unstamped manifest is indistinguishable from a fleet
94    // capture; the renderer will not publish one it cannot attribute.
95    let (source, instance) = SubMsP99Source::from_env();
96    manifest.set_p99_source(source, instance.as_deref());
97
98    // ---------- base (clock-sweep): the baseline, not a feature ----------
99    // Every feature is classified against this. A variant whose lookup lands
100    // within a whisker of the base is a capability, not a latency change, and
101    // classify_feature says so rather than calling it hot-path by default.
102    // The baseline is a p50, because the sweep values are p50s. Handing
103    // classify_feature a base p99 against p50 sweep points compares two
104    // different statistics: the p50 sits below the p99 almost by construction,
105    // so every feature reads as "within 10% of base" and lands auxiliary.
106    let base_p50 = {
107        use subms_block_cache::BlockCache;
108        let mut c: BlockCache<u32, u64> = BlockCache::with_capacity(canon);
109        for k in 0..canon as u32 {
110            c.put(k, k as u64);
111        }
112        let (p50, _) = get_hit(canon, |key| c.get(&key).is_some());
113        p50
114    };
115
116    // ---------- arc: adaptive replacement, recency + frequency lists ----------
117    #[cfg(feature = "arc")]
118    {
119        use subms_block_cache::ArcCache;
120        let sweep: Vec<(usize, u64)> = SIZES
121            .iter()
122            .map(|&n| {
123                let mut c: ArcCache<u32, u64> = ArcCache::with_capacity(n);
124                for k in 0..n as u32 {
125                    c.put(k, k as u64);
126                }
127                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
128                (n, p50)
129            })
130            .collect();
131        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
132
133        let mut c: ArcCache<u32, u64> = ArcCache::with_capacity(canon);
134        for k in 0..canon as u32 {
135            c.put(k, k as u64);
136        }
137        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
138        let (_, put99) = put_evicting(canon, |key| {
139            c.put(key, key as u64);
140        });
141        let mut p99 = BTreeMap::new();
142        p99.insert("get_hit".to_string(), get99);
143        p99.insert("put".to_string(), put99);
144        manifest.set_feature("arc", cat, &p99, &reason);
145    }
146
147    // ---------- tinylfu: frequency-sketch admission ----------
148    #[cfg(feature = "tinylfu")]
149    {
150        use subms_block_cache::TinyLfuCache;
151        let sweep: Vec<(usize, u64)> = SIZES
152            .iter()
153            .map(|&n| {
154                let mut c: TinyLfuCache<u32, u64> = TinyLfuCache::with_capacity(n);
155                for k in 0..n as u32 {
156                    c.put(k, k as u64);
157                }
158                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
159                (n, p50)
160            })
161            .collect();
162        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
163
164        let mut c: TinyLfuCache<u32, u64> = TinyLfuCache::with_capacity(canon);
165        for k in 0..canon as u32 {
166            c.put(k, k as u64);
167        }
168        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
169        let (_, put99) = put_evicting(canon, |key| {
170            c.put(key, key as u64);
171        });
172        let mut p99 = BTreeMap::new();
173        p99.insert("get_hit".to_string(), get99);
174        p99.insert("put".to_string(), put99);
175        manifest.set_feature("tinylfu", cat, &p99, &reason);
176    }
177
178    // ---------- weighted: a byte budget rather than a slot count ----------
179    #[cfg(feature = "weighted")]
180    {
181        use subms_block_cache::WeightedCache;
182        // 1 byte per entry so capacity_bytes == slot capacity; eviction behaves
183        // like the base cache, which isolates the weight bookkeeping itself.
184        let sweep: Vec<(usize, u64)> = SIZES
185            .iter()
186            .map(|&n| {
187                let mut c: WeightedCache<u32, u64> =
188                    WeightedCache::with_capacity_bytes(n, |_v: &u64| 1);
189                for k in 0..n as u32 {
190                    c.put(k, k as u64);
191                }
192                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
193                (n, p50)
194            })
195            .collect();
196        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
197
198        let mut c: WeightedCache<u32, u64> =
199            WeightedCache::with_capacity_bytes(canon, |_v: &u64| 1);
200        for k in 0..canon as u32 {
201            c.put(k, k as u64);
202        }
203        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
204        let (_, put99) = put_evicting(canon, |key| {
205            let _ = c.put(key, key as u64);
206        });
207        let mut p99 = BTreeMap::new();
208        p99.insert("get_hit".to_string(), get99);
209        p99.insert("put".to_string(), put99);
210        manifest.set_feature("weighted", cat, &p99, &reason);
211    }
212
213    // ---------- concurrent-shards: measured single-threaded ----------
214    // Uncontended on purpose. This isolates the sharding INDIRECTION from the
215    // contention it exists to relieve; a multi-threaded number here would say
216    // more about the thread count than about the feature.
217    #[cfg(feature = "concurrent-shards")]
218    {
219        use subms_block_cache::ShardedCache;
220        let sweep: Vec<(usize, u64)> = SIZES
221            .iter()
222            .map(|&n| {
223                let c: ShardedCache<u32, u64> = ShardedCache::with_capacity(n, 16);
224                for k in 0..n as u32 {
225                    c.put(k, k as u64);
226                }
227                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
228                (n, p50)
229            })
230            .collect();
231        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
232
233        let c: ShardedCache<u32, u64> = ShardedCache::with_capacity(canon, 16);
234        for k in 0..canon as u32 {
235            c.put(k, k as u64);
236        }
237        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
238        let (_, put99) = put_evicting(canon, |key| {
239            c.put(key, key as u64);
240        });
241        let mut p99 = BTreeMap::new();
242        p99.insert("get_hit".to_string(), get99);
243        p99.insert("put".to_string(), put99);
244        manifest.set_feature("concurrent-shards", cat, &p99, &reason);
245    }
246
247    // ---------- metrics: hit/miss counters on the lookup path ----------
248    #[cfg(feature = "metrics")]
249    {
250        use subms_block_cache::MetricsCache;
251        let sweep: Vec<(usize, u64)> = SIZES
252            .iter()
253            .map(|&n| {
254                let mut c: MetricsCache<u32, u64> = MetricsCache::with_capacity(n);
255                for k in 0..n as u32 {
256                    c.put(k, k as u64);
257                }
258                let (p50, _) = get_hit(n, |key| c.get(&key).is_some());
259                (n, p50)
260            })
261            .collect();
262        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
263
264        let mut c: MetricsCache<u32, u64> = MetricsCache::with_capacity(canon);
265        for k in 0..canon as u32 {
266            c.put(k, k as u64);
267        }
268        let (_, get99) = get_hit(canon, |key| c.get(&key).is_some());
269        let (_, put99) = put_evicting(canon, |key| {
270            c.put(key, key as u64);
271        });
272        let mut p99 = BTreeMap::new();
273        p99.insert("get_hit".to_string(), get99);
274        p99.insert("put".to_string(), put99);
275        manifest.set_feature("metrics", cat, &p99, &reason);
276    }
277
278    std::fs::create_dir_all(path.parent().unwrap())?;
279    std::fs::write(&path, manifest.to_json())?;
280    io::stdout().write_all(manifest.to_json().as_bytes())?;
281    Ok(())
282}
Source

pub fn remove(&self, key: &K) -> Option<V>

Invalidate key in its own shard. Only that shard is locked.

Source

pub fn clear(&self)

Drop every entry. Shards are cleared one at a time, so a concurrent writer can land in an already-cleared shard - this is a bulk invalidation, not a global barrier.

Auto Trait Implementations§

§

impl<K, V> !Freeze for ShardedCache<K, V>

§

impl<K, V> RefUnwindSafe for ShardedCache<K, V>

§

impl<K, V> Send for ShardedCache<K, V>
where K: Send, V: Send,

§

impl<K, V> Sync for ShardedCache<K, V>
where K: Send, V: Send,

§

impl<K, V> Unpin for ShardedCache<K, V>
where K: Unpin, V: Unpin,

§

impl<K, V> UnsafeUnpin for ShardedCache<K, V>

§

impl<K, V> UnwindSafe for ShardedCache<K, V>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.