From 778d859b902c7d5ca05101ff00754efe3e609d23 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 2 Sep 2026 21:04:44 +0800 Subject: [PATCH] perf: streamline sketch update paths --- CHANGELOG.md | 1 + benchmarks/bloom/update.rs | 20 +++++++++++++++++++ datasketches/src/bloom/sketch.rs | 20 ++++++++++--------- .../src/thetafamily/common/constants.rs | 2 -- .../src/thetafamily/common/hash_table.rs | 11 ++++------ 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 427ec34c..e043ec0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All significant changes to this project will be documented in this file. * Improve truncated-input diagnostics across sketch deserializers. * Improve hash-backed sketch update performance for integer and raw-byte inputs. +* Improve Bloom filter membership-and-insert performance and simplify Theta-family hash table thresholds. ### Bug fixes diff --git a/benchmarks/bloom/update.rs b/benchmarks/bloom/update.rs index 2aa6b810..7227b21e 100644 --- a/benchmarks/bloom/update.rs +++ b/benchmarks/bloom/update.rs @@ -51,3 +51,23 @@ fn bytes_32(bencher: Bencher) { black_box(filter) }); } + +#[divan::bench] +fn contains_and_insert_present_u64(bencher: Bencher) { + let mut populated = BloomFilterBuilder::with_accuracy(ITEMS as u64, 0.01) + .build() + .unwrap(); + for value in 0..ITEMS as u64 { + populated.insert(value); + } + + bencher + .counter(ItemsCount::new(ITEMS)) + .with_inputs(|| populated.clone()) + .bench_local_values(|mut filter| { + for value in 0..ITEMS as u64 { + black_box(filter.contains_and_insert(black_box(&value))); + } + filter + }); +} diff --git a/datasketches/src/bloom/sketch.rs b/datasketches/src/bloom/sketch.rs index 8bf39220..d0d4b6bb 100644 --- a/datasketches/src/bloom/sketch.rs +++ b/datasketches/src/bloom/sketch.rs @@ -101,9 +101,7 @@ impl BloomFilter { /// ``` pub fn contains_and_insert(&mut self, item: &T) -> bool { let (h0, h1) = self.compute_hash(item); - let was_present = self.check_bits(h0, h1); - self.set_bits(h0, h1); - was_present + self.set_bits(h0, h1) } /// Inserts an item into the filter. @@ -564,12 +562,14 @@ impl BloomFilter { true } - /// Sets all k bits for the given hash values. - fn set_bits(&mut self, h0: u64, h1: u64) { + /// Sets all k bits and returns whether they were already set. + fn set_bits(&mut self, h0: u64, h1: u64) -> bool { + let mut were_all_set = true; for i in 1..=self.num_hashes { let bit_index = self.compute_bit_index(h0, h1, i); - self.set_bit(bit_index); + were_all_set &= self.set_bit(bit_index); } + were_all_set } /// Computes a bit index using double hashing (Kirsch-Mitzenmacher). @@ -593,16 +593,18 @@ impl BloomFilter { (self.bit_array[word_index] & mask) != 0 } - /// Sets a single bit and updates the count if it wasn't already set. - fn set_bit(&mut self, bit_index: usize) { + /// Sets a single bit and returns whether it was already set. + fn set_bit(&mut self, bit_index: usize) -> bool { let word_index = bit_index >> 6; // Equivalent to bit_index / 64 let bit_offset = bit_index & 63; // Equivalent to bit_index % 64 let mask = 1u64 << bit_offset; + let was_set = (self.bit_array[word_index] & mask) != 0; - if (self.bit_array[word_index] & mask) == 0 { + if !was_set { self.bit_array[word_index] |= mask; self.num_bits_set += 1; } + was_set } /// Returns the estimated size of the filter in bytes. diff --git a/datasketches/src/thetafamily/common/constants.rs b/datasketches/src/thetafamily/common/constants.rs index da1480b2..8f0ee6e6 100644 --- a/datasketches/src/thetafamily/common/constants.rs +++ b/datasketches/src/thetafamily/common/constants.rs @@ -27,8 +27,6 @@ pub const MAX_LG_K: u8 = 26; /// Default log2 of K. pub const DEFAULT_LG_K: u8 = 12; -/// Resize threshold (0.5 = 50% load factor). -pub const HASH_TABLE_RESIZE_THRESHOLD: f64 = 0.5; /// Rebuild threshold (15/16 = 93.75% load factor). pub const HASH_TABLE_REBUILD_THRESHOLD: f64 = 15.0 / 16.0; diff --git a/datasketches/src/thetafamily/common/hash_table.rs b/datasketches/src/thetafamily/common/hash_table.rs index a115a2e0..5525f748 100644 --- a/datasketches/src/thetafamily/common/hash_table.rs +++ b/datasketches/src/thetafamily/common/hash_table.rs @@ -24,8 +24,6 @@ use crate::error::ErrorKind; use crate::hash::MurmurHash3X64128; use crate::hash::compute_seed_hash; use crate::thetacommon::SketchEntry; -use crate::thetacommon::constants::HASH_TABLE_REBUILD_THRESHOLD; -use crate::thetacommon::constants::HASH_TABLE_RESIZE_THRESHOLD; use crate::thetacommon::constants::MAX_LG_K; use crate::thetacommon::constants::MAX_THETA; use crate::thetacommon::constants::MIN_LG_K; @@ -244,12 +242,11 @@ where /// Return the current resize or rebuild capacity threshold. pub fn capacity_threshold(&self) -> usize { - let fraction = if self.lg_cur_size <= self.lg_nom_size { - HASH_TABLE_RESIZE_THRESHOLD + if self.lg_cur_size <= self.lg_nom_size { + self.entries.len() / 2 } else { - HASH_TABLE_REBUILD_THRESHOLD - }; - (fraction * self.entries.len() as f64) as usize + self.entries.len() - self.entries.len() / 16 + } } /// Trim the table to nominal size k.