Skip to main content

BlockCache

Struct BlockCache 

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

Implementations§

Source§

impl<K: Hash + Eq + Clone, V> BlockCache<K, V>

Source

pub fn with_capacity(capacity: usize) -> Self

Examples found in repository?
examples/growth_main.rs (line 103)
82fn main() -> ExitCode {
83    let mut raw = String::new();
84    if io::stdin().read_to_string(&mut raw).is_err() {
85        eprintln!("growth_main: failed to read stdin");
86        return ExitCode::FAILURE;
87    }
88    let mut map = BTreeMap::new();
89    for line in raw.lines() {
90        let line = line.trim();
91        if line.is_empty() || line.starts_with('#') {
92            continue;
93        }
94        if let Some((k, v)) = line.split_once('=') {
95            map.insert(k.trim().to_string(), v.trim().to_string());
96        }
97    }
98    let rounds = parse_usize(&map, "rounds", 50);
99    let capacity = parse_usize(&map, "capacity", 1024);
100    let inserts_per_round = parse_usize(&map, "inserts_per_round", 20_000);
101
102    let mut recipe = CacheChurn {
103        cache: BlockCache::with_capacity(capacity),
104        capacity,
105        rounds,
106        inserts_per_round,
107        value: vec![0u8; VALUE_BYTES],
108        next: 0,
109    };
110    let report = grow(&mut recipe, "rust");
111
112    if growth_to_json(&report, &mut io::stdout().lock()).is_err() {
113        eprintln!("growth_main: failed to write json");
114        return ExitCode::FAILURE;
115    }
116    ExitCode::SUCCESS
117}
More examples
Hide additional examples
examples/sample_app.rs (line 49)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}
examples/perf_features.rs (line 108)
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 capacity(&self) -> usize

Source

pub fn len(&self) -> usize

Examples found in repository?
examples/growth_main.rs (line 56)
55    fn memory_bytes(&mut self) -> u64 {
56        self.cache.len() as u64 * ENTRY_BYTES
57    }
58    fn live_bytes(&mut self) -> u64 {
59        // A cache holds only live entries, so resident == live: amplification 1x
60        // is the healthy shape (every resident byte is one the cache chose to keep).
61        self.cache.len() as u64 * ENTRY_BYTES
62    }
63    fn structures(&mut self) -> Vec<(String, u64)> {
64        vec![("entries".to_string(), self.cache.len() as u64)]
65    }
More examples
Hide additional examples
examples/sample_app.rs (line 58)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}
Source

pub fn is_empty(&self) -> bool

Examples found in repository?
examples/sample_app.rs (line 80)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}
Source

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

Get a reference to the value for key, marking the slot referenced for the clock sweep.

Examples found in repository?
examples/sample_app.rs (line 54)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}
More examples
Hide additional examples
examples/perf_features.rs (line 112)
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(&mut self, key: K, value: V) -> Option<(K, V)>

Insert or update. Returns the evicted (key, value) pair if eviction happened to make room.

Examples found in repository?
examples/growth_main.rs (line 52)
49    fn op(&mut self, _round: usize, _i: usize) {
50        // A fresh distinct key every op: nothing is ever re-hit, so a naive
51        // unbounded map would grow to millions - the LRU must evict to stay flat.
52        self.cache.put(self.next, self.value.clone());
53        self.next += 1;
54    }
More examples
Hide additional examples
examples/sample_app.rs (line 55)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}
examples/perf_features.rs (line 110)
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(&mut self, key: &K) -> Option<V>

Invalidate key, returning its value. The vacated slot is refilled by the next insert; the hand does not move, so removal costs one map lookup and one slot store.

Examples found in repository?
examples/sample_app.rs (line 77)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}
Source

pub fn clear(&mut self)

Drop every entry and reset the hand. Capacity is unchanged.

Examples found in repository?
examples/sample_app.rs (line 79)
46fn base_page_cache() {
47    println!("== base: block cache in front of a cold columnar store ==");
48    const CAP: usize = 4;
49    let mut cache: BlockCache<u64, String> = BlockCache::with_capacity(CAP);
50
51    let hot = [100u64, 101, 102, 103];
52    let mut cold_reads = 0;
53    for &id in &hot {
54        assert!(cache.get(&id).is_none(), "cold on first touch");
55        cache.put(id, read_block(id));
56        cold_reads += 1;
57    }
58    println!("  warmed {} pages, {cold_reads} cold reads", cache.len());
59    assert_eq!(cache.len(), CAP);
60
61    let mut served = 0;
62    for &id in &hot {
63        assert!(cache.get(&id).is_some(), "resident page must never miss");
64        served += 1;
65    }
66    println!("  re-read the hot set: {served} served, 0 cold reads");
67    assert_eq!(served, CAP);
68
69    let (victim, _) = cache
70        .put(200, read_block(200))
71        .expect("full cache must evict to admit a new page");
72    println!("  admitted page 200, evicted page {victim}");
73    assert_eq!(cache.len(), CAP, "capacity is a hard bound");
74
75    // A compaction rewrote page 200, so the cached copy is stale. Invalidate
76    // it rather than waiting for the hand to come round.
77    assert!(cache.remove(&200).is_some(), "stale page must be dropped");
78    println!("  invalidated page 200, {} pages resident", cache.len());
79    cache.clear();
80    assert!(cache.is_empty(), "clear drops the whole segment");
81    println!("  segment dropped, cache empty, capacity still {CAP}");
82}

Auto Trait Implementations§

§

impl<K, V> Freeze for BlockCache<K, V>

§

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

§

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

§

impl<K, V> Sync for BlockCache<K, V>
where K: Sync, V: Sync,

§

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

§

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

§

impl<K, V> UnwindSafe for BlockCache<K, V>
where K: UnwindSafe, V: UnwindSafe,

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.