1use std::collections::BTreeMap;
32use std::io::{self, Write};
33use std::path::PathBuf;
34
35use subms::{
36 SubMsFeatureManifest, SubMsLcg, SubMsP99Source, SubMsPerfHarness, classify_feature, summarize,
37};
38
39const SIZES: [usize; 3] = [4_096, 32_768, 262_144];
41const SEED: u64 = 0;
42
43fn stage_stats(h: &SubMsPerfHarness, name: &str) -> (u64, u64) {
44 summarize(h)
45 .stages
46 .iter()
47 .find(|s| s.name == name)
48 .map_or((0, 0), |s| (s.p50_ns, s.p99_ns))
49}
50
51fn get_hit(n: usize, mut get: impl FnMut(u32) -> bool) -> (u64, u64) {
53 let mut rng = SubMsLcg::new(SEED);
54 let mut h = SubMsPerfHarness::new("block-cache-feature", "rust");
55 {
56 let st = h.stage("op", n);
57 for _ in 0..n {
58 let key = rng.next_u32() % (n as u32);
59 st.time(|| {
60 let _ = get(key);
61 });
62 }
63 }
64 stage_stats(&h, "op")
65}
66
67fn put_evicting(n: usize, mut put: impl FnMut(u32)) -> (u64, u64) {
70 let base = n as u32;
71 let mut h = SubMsPerfHarness::new("block-cache-feature", "rust");
72 {
73 let st = h.stage("op", n);
74 for i in 0..n {
75 let key = base + i as u32;
76 st.time(|| put(key));
77 }
78 }
79 stage_stats(&h, "op")
80}
81
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 let (source, instance) = SubMsP99Source::from_env();
96 manifest.set_p99_source(source, instance.as_deref());
97
98 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 #[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 #[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 #[cfg(feature = "weighted")]
180 {
181 use subms_block_cache::WeightedCache;
182 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 #[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 #[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}