Skip to main content

DynamicCuckooFilter

Struct DynamicCuckooFilter 

Source
pub struct DynamicCuckooFilter { /* private fields */ }

Implementations§

Source§

impl DynamicCuckooFilter

Source

pub fn new(initial_capacity: usize) -> Self

Build a dynamic cuckoo filter starting sized for initial_capacity entries. Auto-grows at 95% load.

Examples found in repository?
examples/perf_features.rs (line 157)
80fn main() -> io::Result<()> {
81    let canon = SIZES[SIZES.len() - 1];
82    let canon_keys = keys(canon);
83
84    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
85        .join("..")
86        .join(".subms")
87        .join("features")
88        .join("rust.json");
89    let existing = std::fs::read_to_string(&path).unwrap_or_default();
90    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
91    // Stamp the box these numbers came from. The bench runs wherever it is
92    // invoked, so an unstamped manifest is indistinguishable from a fleet
93    // capture; the renderer will not publish one it cannot attribute.
94    let (source, instance) = SubMsP99Source::from_env();
95    manifest.set_p99_source(source, instance.as_deref());
96
97    // ---------- base: the baseline, not a feature ----------
98    // Every feature is classified against this. A variant whose lookup lands at
99    // or under the base costs nothing on the hot path, and classify_feature says
100    // so rather than calling it hot-path by default.
101    let base_p50 = {
102        let mut f = CuckooFilter::with_capacity(canon);
103        for k in &canon_keys {
104            f.insert(k);
105        }
106        let (p50, _) = keyed(&canon_keys, |k| {
107            let _ = f.contains(k);
108        });
109        p50
110    };
111
112    // ---------- variable-fingerprint: wider tag, lower FPR ----------
113    #[cfg(feature = "variable-fingerprint")]
114    {
115        use subms_cuckoo_filter::{FingerprintWidth, VariableFpCuckooFilter};
116        let sweep: Vec<(usize, u64)> = SIZES
117            .iter()
118            .map(|&n| {
119                let ks = keys(n);
120                let mut f = VariableFpCuckooFilter::new(n, FingerprintWidth::Sixteen);
121                for k in &ks {
122                    f.insert(k);
123                }
124                let (p50, _) = keyed(&ks, |k| {
125                    let _ = f.contains(k);
126                });
127                (n, p50)
128            })
129            .collect();
130        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
131
132        let mut f = VariableFpCuckooFilter::new(canon, FingerprintWidth::Sixteen);
133        let (_, insert99) = keyed(&canon_keys, |k| {
134            f.insert(k);
135        });
136        let (_, lookup99) = keyed(&canon_keys, |k| {
137            let _ = f.contains(k);
138        });
139        let (_, delete99) = keyed(&canon_keys, |k| {
140            f.delete(k);
141        });
142        let mut p99 = BTreeMap::new();
143        p99.insert("insert".to_string(), insert99);
144        p99.insert("lookup".to_string(), lookup99);
145        p99.insert("delete".to_string(), delete99);
146        manifest.set_feature("variable-fingerprint", cat, &p99, &reason);
147    }
148
149    // ---------- dynamic: grows rather than refusing at load factor ----------
150    #[cfg(feature = "dynamic")]
151    {
152        use subms_cuckoo_filter::DynamicCuckooFilter;
153        let sweep: Vec<(usize, u64)> = SIZES
154            .iter()
155            .map(|&n| {
156                let ks = keys(n);
157                let mut f = DynamicCuckooFilter::new(n);
158                for k in &ks {
159                    f.insert(k);
160                }
161                let (p50, _) = keyed(&ks, |k| {
162                    let _ = f.contains(k);
163                });
164                (n, p50)
165            })
166            .collect();
167        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
168
169        let mut f = DynamicCuckooFilter::new(canon);
170        let (_, insert99) = keyed(&canon_keys, |k| {
171            f.insert(k);
172        });
173        let (_, lookup99) = keyed(&canon_keys, |k| {
174            let _ = f.contains(k);
175        });
176        let (_, delete99) = keyed(&canon_keys, |k| {
177            f.delete(k);
178        });
179        let mut p99 = BTreeMap::new();
180        p99.insert("insert".to_string(), insert99);
181        p99.insert("lookup".to_string(), lookup99);
182        p99.insert("delete".to_string(), delete99);
183        manifest.set_feature("dynamic", cat, &p99, &reason);
184    }
185
186    // ---------- concurrent-reads: a frozen snapshot readers share ----------
187    // Classified on the SNAPSHOT, not the lookup. The snapshot is a whole-table
188    // copy whose cost is the thing that scales; the lookups against it are
189    // per-op and would classify the same as any other read.
190    #[cfg(feature = "concurrent-reads")]
191    {
192        use subms_cuckoo_filter::CuckooSnapshot;
193        let sweep: Vec<(usize, u64)> = SIZES
194            .iter()
195            .map(|&n| {
196                let ks = keys(n);
197                let mut src = CuckooFilter::with_capacity(n);
198                for k in &ks {
199                    src.insert(k);
200                }
201                // Several samples, not one. A single timed capture at the
202                // SMALLEST size absorbs the first-touch allocation cost, which
203                // inflates the low end of the sweep and flattens the very ratio
204                // the scaling test reads - a whole-table copy then classifies
205                // hot-path, which is exactly backwards.
206                for _ in 0..SNAPSHOT_WARM {
207                    let _ = CuckooSnapshot::capture(&src);
208                }
209                let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
210                {
211                    let st = h.stage("op", SNAPSHOT_REPS);
212                    for _ in 0..SNAPSHOT_REPS {
213                        st.time(|| {
214                            let _ = CuckooSnapshot::capture(&src);
215                        });
216                    }
217                }
218                let (p50, _) = stage_stats(&h, "op");
219                (n, p50)
220            })
221            .collect();
222        // PINNED structural, not measured. `CuckooSnapshot::capture` is a
223        // `to_vec()` of the whole bucket array - unambiguously O(N) from the
224        // source - but this sweep cannot demonstrate it on a dev box: even with
225        // warmup discarded the smallest size measures ~7us against ~3us at 8x
226        // the size, a non-monotonic curve whose min/max ratio reads ~2x over a
227        // 64x size range, so the scaling test calls it flat and an O(N) memcpy
228        // classifies hot-path. Recording that would be a false claim about the
229        // one op on this page that genuinely is not per-op, so the category is
230        // pinned and `perfReason` says it was overridden rather than measured.
231        // Revisit on a fleet capture, where the curve should separate.
232        //
233        // No base comparison either: a whole-table copy is not the same kind of
234        // operation as a per-key lookup, so a delta against it means nothing.
235        let (cat, reason) =
236            classify_feature(&sweep, None, Some(subms::SubMsFeatureCategory::Structural));
237
238        let mut src = CuckooFilter::with_capacity(canon);
239        for k in &canon_keys {
240            src.insert(k);
241        }
242        for _ in 0..SNAPSHOT_WARM {
243            let _ = CuckooSnapshot::capture(&src);
244        }
245        let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
246        let snap = {
247            let st = h.stage("op", SNAPSHOT_REPS);
248            for _ in 0..SNAPSHOT_REPS - 1 {
249                st.time(|| {
250                    let _ = CuckooSnapshot::capture(&src);
251                });
252            }
253            st.time(|| CuckooSnapshot::capture(&src))
254        };
255        let (_, snap99) = stage_stats(&h, "op");
256        let (_, lookup99) = keyed(&canon_keys, |k| {
257            let _ = snap.contains(k);
258        });
259        let mut p99 = BTreeMap::new();
260        p99.insert("snapshot".to_string(), snap99);
261        p99.insert("lookup_on_snapshot".to_string(), lookup99);
262        manifest.set_feature("concurrent-reads", cat, &p99, &reason);
263    }
264
265    // ---------- compressed-buckets: tighter memory per bucket ----------
266    #[cfg(feature = "compressed-buckets")]
267    {
268        use subms_cuckoo_filter::CompressedCuckooFilter;
269        let sweep: Vec<(usize, u64)> = SIZES
270            .iter()
271            .map(|&n| {
272                let ks = keys(n);
273                let mut f = CompressedCuckooFilter::with_capacity(n);
274                for k in &ks {
275                    f.insert(k);
276                }
277                let (p50, _) = keyed(&ks, |k| {
278                    let _ = f.contains(k);
279                });
280                (n, p50)
281            })
282            .collect();
283        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
284
285        let mut f = CompressedCuckooFilter::with_capacity(canon);
286        let (_, insert99) = keyed(&canon_keys, |k| {
287            f.insert(k);
288        });
289        let (_, lookup99) = keyed(&canon_keys, |k| {
290            let _ = f.contains(k);
291        });
292        let (_, delete99) = keyed(&canon_keys, |k| {
293            f.delete(k);
294        });
295        let mut p99 = BTreeMap::new();
296        p99.insert("insert".to_string(), insert99);
297        p99.insert("lookup".to_string(), lookup99);
298        p99.insert("delete".to_string(), delete99);
299        manifest.set_feature("compressed-buckets", cat, &p99, &reason);
300    }
301
302    std::fs::create_dir_all(path.parent().unwrap())?;
303    std::fs::write(&path, manifest.to_json())?;
304    io::stdout().write_all(manifest.to_json().as_bytes())?;
305    Ok(())
306}
Source

pub fn with_threshold(initial_capacity: usize, grow_threshold: f64) -> Self

Build with a custom grow threshold in (0.0, 1.0). Lower thresholds grow earlier (more layers, lower per-layer pressure); higher thresholds delay growth at the cost of risk-of-rejection.

Examples found in repository?
examples/sample_app.rs (line 208)
205fn dynamic_dedup_window() {
206    use subms_cuckoo_filter::DynamicCuckooFilter;
207    println!("\n== dynamic: an intraday dedup window that grows itself ==");
208    let mut seen = DynamicCuckooFilter::with_threshold(1_000, 0.5);
209    for i in 0..20_000u32 {
210        seen.insert(&format!("MSG-{i}"));
211    }
212    println!(
213        "  20k ids -> {} layers, active load {:.2}",
214        seen.layer_count(),
215        seen.load_factor()
216    );
217    assert!(
218        seen.layer_count() > 1,
219        "the window grew past its initial sizing"
220    );
221    for i in 0..20_000u32 {
222        assert!(
223            seen.contains(&format!("MSG-{i}")),
224            "no id dropped as the window grew"
225        );
226    }
227}
Source

pub fn layer_count(&self) -> usize

Examples found in repository?
examples/sample_app.rs (line 214)
205fn dynamic_dedup_window() {
206    use subms_cuckoo_filter::DynamicCuckooFilter;
207    println!("\n== dynamic: an intraday dedup window that grows itself ==");
208    let mut seen = DynamicCuckooFilter::with_threshold(1_000, 0.5);
209    for i in 0..20_000u32 {
210        seen.insert(&format!("MSG-{i}"));
211    }
212    println!(
213        "  20k ids -> {} layers, active load {:.2}",
214        seen.layer_count(),
215        seen.load_factor()
216    );
217    assert!(
218        seen.layer_count() > 1,
219        "the window grew past its initial sizing"
220    );
221    for i in 0..20_000u32 {
222        assert!(
223            seen.contains(&format!("MSG-{i}")),
224            "no id dropped as the window grew"
225        );
226    }
227}
Source

pub fn len(&self) -> usize

Source

pub fn is_empty(&self) -> bool

Source

pub fn insert(&mut self, key: &str) -> bool

Insert a key into the active layer. Grows if the active layer crosses the load threshold OR rejects the insert outright.

Examples found in repository?
examples/sample_app.rs (line 210)
205fn dynamic_dedup_window() {
206    use subms_cuckoo_filter::DynamicCuckooFilter;
207    println!("\n== dynamic: an intraday dedup window that grows itself ==");
208    let mut seen = DynamicCuckooFilter::with_threshold(1_000, 0.5);
209    for i in 0..20_000u32 {
210        seen.insert(&format!("MSG-{i}"));
211    }
212    println!(
213        "  20k ids -> {} layers, active load {:.2}",
214        seen.layer_count(),
215        seen.load_factor()
216    );
217    assert!(
218        seen.layer_count() > 1,
219        "the window grew past its initial sizing"
220    );
221    for i in 0..20_000u32 {
222        assert!(
223            seen.contains(&format!("MSG-{i}")),
224            "no id dropped as the window grew"
225        );
226    }
227}
More examples
Hide additional examples
examples/perf_features.rs (line 159)
80fn main() -> io::Result<()> {
81    let canon = SIZES[SIZES.len() - 1];
82    let canon_keys = keys(canon);
83
84    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
85        .join("..")
86        .join(".subms")
87        .join("features")
88        .join("rust.json");
89    let existing = std::fs::read_to_string(&path).unwrap_or_default();
90    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
91    // Stamp the box these numbers came from. The bench runs wherever it is
92    // invoked, so an unstamped manifest is indistinguishable from a fleet
93    // capture; the renderer will not publish one it cannot attribute.
94    let (source, instance) = SubMsP99Source::from_env();
95    manifest.set_p99_source(source, instance.as_deref());
96
97    // ---------- base: the baseline, not a feature ----------
98    // Every feature is classified against this. A variant whose lookup lands at
99    // or under the base costs nothing on the hot path, and classify_feature says
100    // so rather than calling it hot-path by default.
101    let base_p50 = {
102        let mut f = CuckooFilter::with_capacity(canon);
103        for k in &canon_keys {
104            f.insert(k);
105        }
106        let (p50, _) = keyed(&canon_keys, |k| {
107            let _ = f.contains(k);
108        });
109        p50
110    };
111
112    // ---------- variable-fingerprint: wider tag, lower FPR ----------
113    #[cfg(feature = "variable-fingerprint")]
114    {
115        use subms_cuckoo_filter::{FingerprintWidth, VariableFpCuckooFilter};
116        let sweep: Vec<(usize, u64)> = SIZES
117            .iter()
118            .map(|&n| {
119                let ks = keys(n);
120                let mut f = VariableFpCuckooFilter::new(n, FingerprintWidth::Sixteen);
121                for k in &ks {
122                    f.insert(k);
123                }
124                let (p50, _) = keyed(&ks, |k| {
125                    let _ = f.contains(k);
126                });
127                (n, p50)
128            })
129            .collect();
130        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
131
132        let mut f = VariableFpCuckooFilter::new(canon, FingerprintWidth::Sixteen);
133        let (_, insert99) = keyed(&canon_keys, |k| {
134            f.insert(k);
135        });
136        let (_, lookup99) = keyed(&canon_keys, |k| {
137            let _ = f.contains(k);
138        });
139        let (_, delete99) = keyed(&canon_keys, |k| {
140            f.delete(k);
141        });
142        let mut p99 = BTreeMap::new();
143        p99.insert("insert".to_string(), insert99);
144        p99.insert("lookup".to_string(), lookup99);
145        p99.insert("delete".to_string(), delete99);
146        manifest.set_feature("variable-fingerprint", cat, &p99, &reason);
147    }
148
149    // ---------- dynamic: grows rather than refusing at load factor ----------
150    #[cfg(feature = "dynamic")]
151    {
152        use subms_cuckoo_filter::DynamicCuckooFilter;
153        let sweep: Vec<(usize, u64)> = SIZES
154            .iter()
155            .map(|&n| {
156                let ks = keys(n);
157                let mut f = DynamicCuckooFilter::new(n);
158                for k in &ks {
159                    f.insert(k);
160                }
161                let (p50, _) = keyed(&ks, |k| {
162                    let _ = f.contains(k);
163                });
164                (n, p50)
165            })
166            .collect();
167        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
168
169        let mut f = DynamicCuckooFilter::new(canon);
170        let (_, insert99) = keyed(&canon_keys, |k| {
171            f.insert(k);
172        });
173        let (_, lookup99) = keyed(&canon_keys, |k| {
174            let _ = f.contains(k);
175        });
176        let (_, delete99) = keyed(&canon_keys, |k| {
177            f.delete(k);
178        });
179        let mut p99 = BTreeMap::new();
180        p99.insert("insert".to_string(), insert99);
181        p99.insert("lookup".to_string(), lookup99);
182        p99.insert("delete".to_string(), delete99);
183        manifest.set_feature("dynamic", cat, &p99, &reason);
184    }
185
186    // ---------- concurrent-reads: a frozen snapshot readers share ----------
187    // Classified on the SNAPSHOT, not the lookup. The snapshot is a whole-table
188    // copy whose cost is the thing that scales; the lookups against it are
189    // per-op and would classify the same as any other read.
190    #[cfg(feature = "concurrent-reads")]
191    {
192        use subms_cuckoo_filter::CuckooSnapshot;
193        let sweep: Vec<(usize, u64)> = SIZES
194            .iter()
195            .map(|&n| {
196                let ks = keys(n);
197                let mut src = CuckooFilter::with_capacity(n);
198                for k in &ks {
199                    src.insert(k);
200                }
201                // Several samples, not one. A single timed capture at the
202                // SMALLEST size absorbs the first-touch allocation cost, which
203                // inflates the low end of the sweep and flattens the very ratio
204                // the scaling test reads - a whole-table copy then classifies
205                // hot-path, which is exactly backwards.
206                for _ in 0..SNAPSHOT_WARM {
207                    let _ = CuckooSnapshot::capture(&src);
208                }
209                let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
210                {
211                    let st = h.stage("op", SNAPSHOT_REPS);
212                    for _ in 0..SNAPSHOT_REPS {
213                        st.time(|| {
214                            let _ = CuckooSnapshot::capture(&src);
215                        });
216                    }
217                }
218                let (p50, _) = stage_stats(&h, "op");
219                (n, p50)
220            })
221            .collect();
222        // PINNED structural, not measured. `CuckooSnapshot::capture` is a
223        // `to_vec()` of the whole bucket array - unambiguously O(N) from the
224        // source - but this sweep cannot demonstrate it on a dev box: even with
225        // warmup discarded the smallest size measures ~7us against ~3us at 8x
226        // the size, a non-monotonic curve whose min/max ratio reads ~2x over a
227        // 64x size range, so the scaling test calls it flat and an O(N) memcpy
228        // classifies hot-path. Recording that would be a false claim about the
229        // one op on this page that genuinely is not per-op, so the category is
230        // pinned and `perfReason` says it was overridden rather than measured.
231        // Revisit on a fleet capture, where the curve should separate.
232        //
233        // No base comparison either: a whole-table copy is not the same kind of
234        // operation as a per-key lookup, so a delta against it means nothing.
235        let (cat, reason) =
236            classify_feature(&sweep, None, Some(subms::SubMsFeatureCategory::Structural));
237
238        let mut src = CuckooFilter::with_capacity(canon);
239        for k in &canon_keys {
240            src.insert(k);
241        }
242        for _ in 0..SNAPSHOT_WARM {
243            let _ = CuckooSnapshot::capture(&src);
244        }
245        let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
246        let snap = {
247            let st = h.stage("op", SNAPSHOT_REPS);
248            for _ in 0..SNAPSHOT_REPS - 1 {
249                st.time(|| {
250                    let _ = CuckooSnapshot::capture(&src);
251                });
252            }
253            st.time(|| CuckooSnapshot::capture(&src))
254        };
255        let (_, snap99) = stage_stats(&h, "op");
256        let (_, lookup99) = keyed(&canon_keys, |k| {
257            let _ = snap.contains(k);
258        });
259        let mut p99 = BTreeMap::new();
260        p99.insert("snapshot".to_string(), snap99);
261        p99.insert("lookup_on_snapshot".to_string(), lookup99);
262        manifest.set_feature("concurrent-reads", cat, &p99, &reason);
263    }
264
265    // ---------- compressed-buckets: tighter memory per bucket ----------
266    #[cfg(feature = "compressed-buckets")]
267    {
268        use subms_cuckoo_filter::CompressedCuckooFilter;
269        let sweep: Vec<(usize, u64)> = SIZES
270            .iter()
271            .map(|&n| {
272                let ks = keys(n);
273                let mut f = CompressedCuckooFilter::with_capacity(n);
274                for k in &ks {
275                    f.insert(k);
276                }
277                let (p50, _) = keyed(&ks, |k| {
278                    let _ = f.contains(k);
279                });
280                (n, p50)
281            })
282            .collect();
283        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
284
285        let mut f = CompressedCuckooFilter::with_capacity(canon);
286        let (_, insert99) = keyed(&canon_keys, |k| {
287            f.insert(k);
288        });
289        let (_, lookup99) = keyed(&canon_keys, |k| {
290            let _ = f.contains(k);
291        });
292        let (_, delete99) = keyed(&canon_keys, |k| {
293            f.delete(k);
294        });
295        let mut p99 = BTreeMap::new();
296        p99.insert("insert".to_string(), insert99);
297        p99.insert("lookup".to_string(), lookup99);
298        p99.insert("delete".to_string(), delete99);
299        manifest.set_feature("compressed-buckets", cat, &p99, &reason);
300    }
301
302    std::fs::create_dir_all(path.parent().unwrap())?;
303    std::fs::write(&path, manifest.to_json())?;
304    io::stdout().write_all(manifest.to_json().as_bytes())?;
305    Ok(())
306}
Source

pub fn contains(&self, key: &str) -> bool

Membership over every layer. False positives possible (same FPR model as the base; cumulative FPR is bounded by sum across layers but typically dominated by the active layer).

Examples found in repository?
examples/sample_app.rs (line 223)
205fn dynamic_dedup_window() {
206    use subms_cuckoo_filter::DynamicCuckooFilter;
207    println!("\n== dynamic: an intraday dedup window that grows itself ==");
208    let mut seen = DynamicCuckooFilter::with_threshold(1_000, 0.5);
209    for i in 0..20_000u32 {
210        seen.insert(&format!("MSG-{i}"));
211    }
212    println!(
213        "  20k ids -> {} layers, active load {:.2}",
214        seen.layer_count(),
215        seen.load_factor()
216    );
217    assert!(
218        seen.layer_count() > 1,
219        "the window grew past its initial sizing"
220    );
221    for i in 0..20_000u32 {
222        assert!(
223            seen.contains(&format!("MSG-{i}")),
224            "no id dropped as the window grew"
225        );
226    }
227}
More examples
Hide additional examples
examples/perf_features.rs (line 162)
80fn main() -> io::Result<()> {
81    let canon = SIZES[SIZES.len() - 1];
82    let canon_keys = keys(canon);
83
84    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
85        .join("..")
86        .join(".subms")
87        .join("features")
88        .join("rust.json");
89    let existing = std::fs::read_to_string(&path).unwrap_or_default();
90    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
91    // Stamp the box these numbers came from. The bench runs wherever it is
92    // invoked, so an unstamped manifest is indistinguishable from a fleet
93    // capture; the renderer will not publish one it cannot attribute.
94    let (source, instance) = SubMsP99Source::from_env();
95    manifest.set_p99_source(source, instance.as_deref());
96
97    // ---------- base: the baseline, not a feature ----------
98    // Every feature is classified against this. A variant whose lookup lands at
99    // or under the base costs nothing on the hot path, and classify_feature says
100    // so rather than calling it hot-path by default.
101    let base_p50 = {
102        let mut f = CuckooFilter::with_capacity(canon);
103        for k in &canon_keys {
104            f.insert(k);
105        }
106        let (p50, _) = keyed(&canon_keys, |k| {
107            let _ = f.contains(k);
108        });
109        p50
110    };
111
112    // ---------- variable-fingerprint: wider tag, lower FPR ----------
113    #[cfg(feature = "variable-fingerprint")]
114    {
115        use subms_cuckoo_filter::{FingerprintWidth, VariableFpCuckooFilter};
116        let sweep: Vec<(usize, u64)> = SIZES
117            .iter()
118            .map(|&n| {
119                let ks = keys(n);
120                let mut f = VariableFpCuckooFilter::new(n, FingerprintWidth::Sixteen);
121                for k in &ks {
122                    f.insert(k);
123                }
124                let (p50, _) = keyed(&ks, |k| {
125                    let _ = f.contains(k);
126                });
127                (n, p50)
128            })
129            .collect();
130        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
131
132        let mut f = VariableFpCuckooFilter::new(canon, FingerprintWidth::Sixteen);
133        let (_, insert99) = keyed(&canon_keys, |k| {
134            f.insert(k);
135        });
136        let (_, lookup99) = keyed(&canon_keys, |k| {
137            let _ = f.contains(k);
138        });
139        let (_, delete99) = keyed(&canon_keys, |k| {
140            f.delete(k);
141        });
142        let mut p99 = BTreeMap::new();
143        p99.insert("insert".to_string(), insert99);
144        p99.insert("lookup".to_string(), lookup99);
145        p99.insert("delete".to_string(), delete99);
146        manifest.set_feature("variable-fingerprint", cat, &p99, &reason);
147    }
148
149    // ---------- dynamic: grows rather than refusing at load factor ----------
150    #[cfg(feature = "dynamic")]
151    {
152        use subms_cuckoo_filter::DynamicCuckooFilter;
153        let sweep: Vec<(usize, u64)> = SIZES
154            .iter()
155            .map(|&n| {
156                let ks = keys(n);
157                let mut f = DynamicCuckooFilter::new(n);
158                for k in &ks {
159                    f.insert(k);
160                }
161                let (p50, _) = keyed(&ks, |k| {
162                    let _ = f.contains(k);
163                });
164                (n, p50)
165            })
166            .collect();
167        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
168
169        let mut f = DynamicCuckooFilter::new(canon);
170        let (_, insert99) = keyed(&canon_keys, |k| {
171            f.insert(k);
172        });
173        let (_, lookup99) = keyed(&canon_keys, |k| {
174            let _ = f.contains(k);
175        });
176        let (_, delete99) = keyed(&canon_keys, |k| {
177            f.delete(k);
178        });
179        let mut p99 = BTreeMap::new();
180        p99.insert("insert".to_string(), insert99);
181        p99.insert("lookup".to_string(), lookup99);
182        p99.insert("delete".to_string(), delete99);
183        manifest.set_feature("dynamic", cat, &p99, &reason);
184    }
185
186    // ---------- concurrent-reads: a frozen snapshot readers share ----------
187    // Classified on the SNAPSHOT, not the lookup. The snapshot is a whole-table
188    // copy whose cost is the thing that scales; the lookups against it are
189    // per-op and would classify the same as any other read.
190    #[cfg(feature = "concurrent-reads")]
191    {
192        use subms_cuckoo_filter::CuckooSnapshot;
193        let sweep: Vec<(usize, u64)> = SIZES
194            .iter()
195            .map(|&n| {
196                let ks = keys(n);
197                let mut src = CuckooFilter::with_capacity(n);
198                for k in &ks {
199                    src.insert(k);
200                }
201                // Several samples, not one. A single timed capture at the
202                // SMALLEST size absorbs the first-touch allocation cost, which
203                // inflates the low end of the sweep and flattens the very ratio
204                // the scaling test reads - a whole-table copy then classifies
205                // hot-path, which is exactly backwards.
206                for _ in 0..SNAPSHOT_WARM {
207                    let _ = CuckooSnapshot::capture(&src);
208                }
209                let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
210                {
211                    let st = h.stage("op", SNAPSHOT_REPS);
212                    for _ in 0..SNAPSHOT_REPS {
213                        st.time(|| {
214                            let _ = CuckooSnapshot::capture(&src);
215                        });
216                    }
217                }
218                let (p50, _) = stage_stats(&h, "op");
219                (n, p50)
220            })
221            .collect();
222        // PINNED structural, not measured. `CuckooSnapshot::capture` is a
223        // `to_vec()` of the whole bucket array - unambiguously O(N) from the
224        // source - but this sweep cannot demonstrate it on a dev box: even with
225        // warmup discarded the smallest size measures ~7us against ~3us at 8x
226        // the size, a non-monotonic curve whose min/max ratio reads ~2x over a
227        // 64x size range, so the scaling test calls it flat and an O(N) memcpy
228        // classifies hot-path. Recording that would be a false claim about the
229        // one op on this page that genuinely is not per-op, so the category is
230        // pinned and `perfReason` says it was overridden rather than measured.
231        // Revisit on a fleet capture, where the curve should separate.
232        //
233        // No base comparison either: a whole-table copy is not the same kind of
234        // operation as a per-key lookup, so a delta against it means nothing.
235        let (cat, reason) =
236            classify_feature(&sweep, None, Some(subms::SubMsFeatureCategory::Structural));
237
238        let mut src = CuckooFilter::with_capacity(canon);
239        for k in &canon_keys {
240            src.insert(k);
241        }
242        for _ in 0..SNAPSHOT_WARM {
243            let _ = CuckooSnapshot::capture(&src);
244        }
245        let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
246        let snap = {
247            let st = h.stage("op", SNAPSHOT_REPS);
248            for _ in 0..SNAPSHOT_REPS - 1 {
249                st.time(|| {
250                    let _ = CuckooSnapshot::capture(&src);
251                });
252            }
253            st.time(|| CuckooSnapshot::capture(&src))
254        };
255        let (_, snap99) = stage_stats(&h, "op");
256        let (_, lookup99) = keyed(&canon_keys, |k| {
257            let _ = snap.contains(k);
258        });
259        let mut p99 = BTreeMap::new();
260        p99.insert("snapshot".to_string(), snap99);
261        p99.insert("lookup_on_snapshot".to_string(), lookup99);
262        manifest.set_feature("concurrent-reads", cat, &p99, &reason);
263    }
264
265    // ---------- compressed-buckets: tighter memory per bucket ----------
266    #[cfg(feature = "compressed-buckets")]
267    {
268        use subms_cuckoo_filter::CompressedCuckooFilter;
269        let sweep: Vec<(usize, u64)> = SIZES
270            .iter()
271            .map(|&n| {
272                let ks = keys(n);
273                let mut f = CompressedCuckooFilter::with_capacity(n);
274                for k in &ks {
275                    f.insert(k);
276                }
277                let (p50, _) = keyed(&ks, |k| {
278                    let _ = f.contains(k);
279                });
280                (n, p50)
281            })
282            .collect();
283        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
284
285        let mut f = CompressedCuckooFilter::with_capacity(canon);
286        let (_, insert99) = keyed(&canon_keys, |k| {
287            f.insert(k);
288        });
289        let (_, lookup99) = keyed(&canon_keys, |k| {
290            let _ = f.contains(k);
291        });
292        let (_, delete99) = keyed(&canon_keys, |k| {
293            f.delete(k);
294        });
295        let mut p99 = BTreeMap::new();
296        p99.insert("insert".to_string(), insert99);
297        p99.insert("lookup".to_string(), lookup99);
298        p99.insert("delete".to_string(), delete99);
299        manifest.set_feature("compressed-buckets", cat, &p99, &reason);
300    }
301
302    std::fs::create_dir_all(path.parent().unwrap())?;
303    std::fs::write(&path, manifest.to_json())?;
304    io::stdout().write_all(manifest.to_json().as_bytes())?;
305    Ok(())
306}
Source

pub fn delete(&mut self, key: &str) -> bool

Delete a single occurrence. Probes newest-first so duplicate keys are removed in reverse insertion order.

Examples found in repository?
examples/perf_features.rs (line 177)
80fn main() -> io::Result<()> {
81    let canon = SIZES[SIZES.len() - 1];
82    let canon_keys = keys(canon);
83
84    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
85        .join("..")
86        .join(".subms")
87        .join("features")
88        .join("rust.json");
89    let existing = std::fs::read_to_string(&path).unwrap_or_default();
90    let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
91    // Stamp the box these numbers came from. The bench runs wherever it is
92    // invoked, so an unstamped manifest is indistinguishable from a fleet
93    // capture; the renderer will not publish one it cannot attribute.
94    let (source, instance) = SubMsP99Source::from_env();
95    manifest.set_p99_source(source, instance.as_deref());
96
97    // ---------- base: the baseline, not a feature ----------
98    // Every feature is classified against this. A variant whose lookup lands at
99    // or under the base costs nothing on the hot path, and classify_feature says
100    // so rather than calling it hot-path by default.
101    let base_p50 = {
102        let mut f = CuckooFilter::with_capacity(canon);
103        for k in &canon_keys {
104            f.insert(k);
105        }
106        let (p50, _) = keyed(&canon_keys, |k| {
107            let _ = f.contains(k);
108        });
109        p50
110    };
111
112    // ---------- variable-fingerprint: wider tag, lower FPR ----------
113    #[cfg(feature = "variable-fingerprint")]
114    {
115        use subms_cuckoo_filter::{FingerprintWidth, VariableFpCuckooFilter};
116        let sweep: Vec<(usize, u64)> = SIZES
117            .iter()
118            .map(|&n| {
119                let ks = keys(n);
120                let mut f = VariableFpCuckooFilter::new(n, FingerprintWidth::Sixteen);
121                for k in &ks {
122                    f.insert(k);
123                }
124                let (p50, _) = keyed(&ks, |k| {
125                    let _ = f.contains(k);
126                });
127                (n, p50)
128            })
129            .collect();
130        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
131
132        let mut f = VariableFpCuckooFilter::new(canon, FingerprintWidth::Sixteen);
133        let (_, insert99) = keyed(&canon_keys, |k| {
134            f.insert(k);
135        });
136        let (_, lookup99) = keyed(&canon_keys, |k| {
137            let _ = f.contains(k);
138        });
139        let (_, delete99) = keyed(&canon_keys, |k| {
140            f.delete(k);
141        });
142        let mut p99 = BTreeMap::new();
143        p99.insert("insert".to_string(), insert99);
144        p99.insert("lookup".to_string(), lookup99);
145        p99.insert("delete".to_string(), delete99);
146        manifest.set_feature("variable-fingerprint", cat, &p99, &reason);
147    }
148
149    // ---------- dynamic: grows rather than refusing at load factor ----------
150    #[cfg(feature = "dynamic")]
151    {
152        use subms_cuckoo_filter::DynamicCuckooFilter;
153        let sweep: Vec<(usize, u64)> = SIZES
154            .iter()
155            .map(|&n| {
156                let ks = keys(n);
157                let mut f = DynamicCuckooFilter::new(n);
158                for k in &ks {
159                    f.insert(k);
160                }
161                let (p50, _) = keyed(&ks, |k| {
162                    let _ = f.contains(k);
163                });
164                (n, p50)
165            })
166            .collect();
167        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
168
169        let mut f = DynamicCuckooFilter::new(canon);
170        let (_, insert99) = keyed(&canon_keys, |k| {
171            f.insert(k);
172        });
173        let (_, lookup99) = keyed(&canon_keys, |k| {
174            let _ = f.contains(k);
175        });
176        let (_, delete99) = keyed(&canon_keys, |k| {
177            f.delete(k);
178        });
179        let mut p99 = BTreeMap::new();
180        p99.insert("insert".to_string(), insert99);
181        p99.insert("lookup".to_string(), lookup99);
182        p99.insert("delete".to_string(), delete99);
183        manifest.set_feature("dynamic", cat, &p99, &reason);
184    }
185
186    // ---------- concurrent-reads: a frozen snapshot readers share ----------
187    // Classified on the SNAPSHOT, not the lookup. The snapshot is a whole-table
188    // copy whose cost is the thing that scales; the lookups against it are
189    // per-op and would classify the same as any other read.
190    #[cfg(feature = "concurrent-reads")]
191    {
192        use subms_cuckoo_filter::CuckooSnapshot;
193        let sweep: Vec<(usize, u64)> = SIZES
194            .iter()
195            .map(|&n| {
196                let ks = keys(n);
197                let mut src = CuckooFilter::with_capacity(n);
198                for k in &ks {
199                    src.insert(k);
200                }
201                // Several samples, not one. A single timed capture at the
202                // SMALLEST size absorbs the first-touch allocation cost, which
203                // inflates the low end of the sweep and flattens the very ratio
204                // the scaling test reads - a whole-table copy then classifies
205                // hot-path, which is exactly backwards.
206                for _ in 0..SNAPSHOT_WARM {
207                    let _ = CuckooSnapshot::capture(&src);
208                }
209                let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
210                {
211                    let st = h.stage("op", SNAPSHOT_REPS);
212                    for _ in 0..SNAPSHOT_REPS {
213                        st.time(|| {
214                            let _ = CuckooSnapshot::capture(&src);
215                        });
216                    }
217                }
218                let (p50, _) = stage_stats(&h, "op");
219                (n, p50)
220            })
221            .collect();
222        // PINNED structural, not measured. `CuckooSnapshot::capture` is a
223        // `to_vec()` of the whole bucket array - unambiguously O(N) from the
224        // source - but this sweep cannot demonstrate it on a dev box: even with
225        // warmup discarded the smallest size measures ~7us against ~3us at 8x
226        // the size, a non-monotonic curve whose min/max ratio reads ~2x over a
227        // 64x size range, so the scaling test calls it flat and an O(N) memcpy
228        // classifies hot-path. Recording that would be a false claim about the
229        // one op on this page that genuinely is not per-op, so the category is
230        // pinned and `perfReason` says it was overridden rather than measured.
231        // Revisit on a fleet capture, where the curve should separate.
232        //
233        // No base comparison either: a whole-table copy is not the same kind of
234        // operation as a per-key lookup, so a delta against it means nothing.
235        let (cat, reason) =
236            classify_feature(&sweep, None, Some(subms::SubMsFeatureCategory::Structural));
237
238        let mut src = CuckooFilter::with_capacity(canon);
239        for k in &canon_keys {
240            src.insert(k);
241        }
242        for _ in 0..SNAPSHOT_WARM {
243            let _ = CuckooSnapshot::capture(&src);
244        }
245        let mut h = SubMsPerfHarness::new("cuckoo-feature", "rust");
246        let snap = {
247            let st = h.stage("op", SNAPSHOT_REPS);
248            for _ in 0..SNAPSHOT_REPS - 1 {
249                st.time(|| {
250                    let _ = CuckooSnapshot::capture(&src);
251                });
252            }
253            st.time(|| CuckooSnapshot::capture(&src))
254        };
255        let (_, snap99) = stage_stats(&h, "op");
256        let (_, lookup99) = keyed(&canon_keys, |k| {
257            let _ = snap.contains(k);
258        });
259        let mut p99 = BTreeMap::new();
260        p99.insert("snapshot".to_string(), snap99);
261        p99.insert("lookup_on_snapshot".to_string(), lookup99);
262        manifest.set_feature("concurrent-reads", cat, &p99, &reason);
263    }
264
265    // ---------- compressed-buckets: tighter memory per bucket ----------
266    #[cfg(feature = "compressed-buckets")]
267    {
268        use subms_cuckoo_filter::CompressedCuckooFilter;
269        let sweep: Vec<(usize, u64)> = SIZES
270            .iter()
271            .map(|&n| {
272                let ks = keys(n);
273                let mut f = CompressedCuckooFilter::with_capacity(n);
274                for k in &ks {
275                    f.insert(k);
276                }
277                let (p50, _) = keyed(&ks, |k| {
278                    let _ = f.contains(k);
279                });
280                (n, p50)
281            })
282            .collect();
283        let (cat, reason) = classify_feature(&sweep, Some(base_p50), None);
284
285        let mut f = CompressedCuckooFilter::with_capacity(canon);
286        let (_, insert99) = keyed(&canon_keys, |k| {
287            f.insert(k);
288        });
289        let (_, lookup99) = keyed(&canon_keys, |k| {
290            let _ = f.contains(k);
291        });
292        let (_, delete99) = keyed(&canon_keys, |k| {
293            f.delete(k);
294        });
295        let mut p99 = BTreeMap::new();
296        p99.insert("insert".to_string(), insert99);
297        p99.insert("lookup".to_string(), lookup99);
298        p99.insert("delete".to_string(), delete99);
299        manifest.set_feature("compressed-buckets", cat, &p99, &reason);
300    }
301
302    std::fs::create_dir_all(path.parent().unwrap())?;
303    std::fs::write(&path, manifest.to_json())?;
304    io::stdout().write_all(manifest.to_json().as_bytes())?;
305    Ok(())
306}
Source

pub fn load_factor(&self) -> f64

Examples found in repository?
examples/sample_app.rs (line 215)
205fn dynamic_dedup_window() {
206    use subms_cuckoo_filter::DynamicCuckooFilter;
207    println!("\n== dynamic: an intraday dedup window that grows itself ==");
208    let mut seen = DynamicCuckooFilter::with_threshold(1_000, 0.5);
209    for i in 0..20_000u32 {
210        seen.insert(&format!("MSG-{i}"));
211    }
212    println!(
213        "  20k ids -> {} layers, active load {:.2}",
214        seen.layer_count(),
215        seen.load_factor()
216    );
217    assert!(
218        seen.layer_count() > 1,
219        "the window grew past its initial sizing"
220    );
221    for i in 0..20_000u32 {
222        assert!(
223            seen.contains(&format!("MSG-{i}")),
224            "no id dropped as the window grew"
225        );
226    }
227}

Auto Trait Implementations§

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.