pub struct BlockCache<K, V> { /* private fields */ }Implementations§
Source§impl<K: Hash + Eq + Clone, V> BlockCache<K, V>
impl<K: Hash + Eq + Clone, V> BlockCache<K, V>
Sourcepub fn with_capacity(capacity: usize) -> Self
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
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}pub fn capacity(&self) -> usize
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Examples found in repository?
More 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}Sourcepub fn is_empty(&self) -> bool
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}Sourcepub fn get(&mut self, key: &K) -> Option<&V>
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
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}Sourcepub fn put(&mut self, key: K, value: V) -> Option<(K, V)>
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?
More 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}Sourcepub fn remove(&mut self, key: &K) -> Option<V>
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}Sourcepub fn clear(&mut self)
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>where
K: RefUnwindSafe,
V: RefUnwindSafe,
impl<K, V> Send for BlockCache<K, V>
impl<K, V> Sync for BlockCache<K, V>
impl<K, V> Unpin for BlockCache<K, V>
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more