Skip to content

Commit 52b25c6

Browse files
committed
tune split point
1 parent 9167bb2 commit 52b25c6

1 file changed

Lines changed: 23 additions & 15 deletions

File tree

crates/geo_filters/src/diff_count.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ impl<'a, C: GeoConfig<Diff>> GeoDiffCount<'a, C> {
139139
/// [`Self::from_bit_chunks`], which re-establishes the filter invariants. The resulting filter
140140
/// is identical to the one produced by pushing the same hashes one by one.
141141
pub fn from_hashes(config: C, hashes: impl ExactSizeIterator<Item = u64>) -> Self {
142-
let split = estimate_split_bucket(&config, hashes.len());
142+
let (split, capacity) = estimate_split_bucket(&config, hashes.len());
143143

144144
// Buckets >= split are sparse and collected for sorting; buckets < split are dense and
145145
// folded directly into the bit vector, where repeated buckets cancel via xor. The toggler
146146
// resolves the bit vector's owned storage once, keeping the per-bit work out of the loop.
147-
let mut numbers = Vec::new();
147+
let mut numbers = Vec::with_capacity(capacity);
148148
let mut bits = BitVec::default();
149149
bits.resize(split);
150150
{
@@ -479,23 +479,31 @@ pub(crate) fn xor<C: GeoConfig<Diff>>(
479479
}
480480

481481
/// Estimates the bucket id that separates the sparse most-significant buckets ("numbers") from
482-
/// the dense least-significant buckets ("bits") for a filter built from `n` hashes.
482+
/// the dense least-significant buckets ("bits") for a filter built from `n` hashes, and a capacity
483+
/// to preallocate the `numbers` buffer with (the expected count at or above the split, plus
484+
/// headroom for variance).
483485
///
484-
/// The expected number of hashes falling into buckets `>= s` is `n * phi^s`. We pick `s` such
485-
/// that this is a small multiple of the most-significant bucket budget, so that after parity
486-
/// reduction the most-significant buckets are almost always supplied by the collected numbers.
487-
/// Choosing a slightly lower split only makes the collected set a bit larger; correctness does
488-
/// not depend on the estimate, since [`GeoDiffCount::from_bit_chunks`] re-splits the combined
489-
/// stream regardless.
490-
fn estimate_split_bucket<C: GeoConfig<Diff>>(config: &C, n: usize) -> usize {
491-
let target = 2 * config.max_msb_len();
486+
/// The expected number of hashes falling into buckets `>= s` is `n * phi^s`. We target about
487+
/// `max_msb_len / 2` such hashes: the most-significant buckets do *not* need to be fully supplied
488+
/// by the collected numbers, since [`GeoDiffCount::from_bit_chunks`] re-splits the combined stream
489+
/// and pulls the remainder from the dense bits. Because the buckets are geometric, raising the
490+
/// split by one `bits_per_level` roughly halves the collected set, so a small target keeps the
491+
/// sort cheap while only marginally enlarging the bit vector. Correctness does not depend on the
492+
/// estimate.
493+
fn estimate_split_bucket<C: GeoConfig<Diff>>(config: &C, n: usize) -> (usize, usize) {
494+
let target = config.max_msb_len() / 2;
495+
// The count at or above the split is ~`target` but varies (std ~sqrt(target)), so reserve
496+
// twice as much to avoid reallocating `numbers` -- but never more than the total hash count.
497+
let capacity = (2 * target).min(n);
492498
if n <= target {
493-
return 0;
499+
// Every hash ends up in `numbers` (split == 0).
500+
return (0, capacity);
494501
}
495502
let ratio = target as f64 / n as f64;
496-
let split = (ratio.ln() / config.phi_f64().ln()).floor() as usize;
497-
// No bucket can ever exceed this bound, so never allocate a larger bit vector.
498-
split.min(64 * config.bits_per_level())
503+
let split = ((ratio.ln() / config.phi_f64().ln()).floor() as usize)
504+
// No bucket can ever exceed this bound, so never allocate a larger bit vector.
505+
.min(64 * config.bits_per_level());
506+
(split, capacity)
499507
}
500508

501509
/// Given a slice sorted in descending order, keeps a single copy of every value occurring an odd

0 commit comments

Comments
 (0)