#![deny(unsafe_code)]
#![cfg_attr(feature = "nightly", feature(test))]
#[cfg(feature = "nightly")]
extern crate test;
use serde_derive::{Deserialize, Serialize};
use std::{
hash::{Hash, Hasher},
iter::Iterator,
num::NonZeroU64,
};
mod stable_hasher;
#[derive(Deserialize, Serialize, PartialEq, Clone, Debug)]
struct Bloom {
#[serde(rename = "b", with = "serde_bytes")]
buffer: Box<[u8]>,
#[serde(rename = "k")]
num_slices: NonZeroU64,
}
impl Bloom {
fn new(capacity: usize, error_ratio: f64) -> Bloom {
debug_assert!(capacity >= 1);
debug_assert!(0.0 < error_ratio && error_ratio < 1.0);
let num_slices = ((1.0 / error_ratio).log2()).ceil() as u64;
let slice_len_bits = (capacity as f64 / 2f64.ln()).ceil() as u64;
let total_bits = num_slices * slice_len_bits;
let buffer_bytes = ((total_bits + 7) / 8) as usize;
let mut buffer = Vec::with_capacity(buffer_bytes);
buffer.resize(buffer_bytes, 0);
Bloom {
buffer: buffer.into_boxed_slice(),
num_slices: NonZeroU64::new(num_slices).unwrap(),
}
}
#[inline]
fn index_iterator(&self, mut h1: u64, mut h2: u64) -> impl Iterator<Item = (usize, u8)> {
let slice_len = NonZeroU64::new(self.buffer.len() as u64 * 8 / self.num_slices).unwrap();
debug_assert_ne!(h2, 0, "Second hash can't be 0 for double hashing");
(0..self.num_slices.get()).map(move |i| {
let hi = h1 % slice_len + i * slice_len.get();
h1 = h1.wrapping_add(h2);
h2 = h2.wrapping_add(i);
let idx = (hi / 8) as usize;
let mask = 1u8 << (hi % 8);
(idx, mask)
})
}
#[inline]
fn insert(&mut self, h1: u64, h2: u64) {
for (byte, mask) in self.index_iterator(h1, h2) {
self.buffer[byte] |= mask;
}
}
#[inline]
fn contains(&self, h1: u64, h2: u64) -> bool {
self.index_iterator(h1, h2)
.all(|(byte, mask)| self.buffer[byte] & mask != 0)
}
}
#[inline]
fn double_hashing_hashes<T: Hash>(item: T) -> (u64, u64) {
let mut hasher = stable_hasher::StableHasher::new();
item.hash(&mut hasher);
let h1 = hasher.finish();
0u8.hash(&mut hasher);
let h2 = hasher.finish().max(1);
(h1, h2)
}
#[derive(Deserialize, Serialize, PartialEq, Clone, Debug)]
pub struct GrowableBloom {
#[serde(rename = "b")]
blooms: Vec<Bloom>,
#[serde(rename = "e")]
desired_error_prob: f64,
#[serde(rename = "t")]
est_insertions: usize,
#[serde(rename = "i")]
inserts: usize,
#[serde(rename = "c")]
capacity: usize,
}
impl GrowableBloom {
const GROWTH_FACTOR: usize = 2;
const TIGHTENING_RATIO: f64 = 0.8515625;
#[inline]
pub fn new(desired_error_prob: f64, est_insertions: usize) -> GrowableBloom {
assert!(0.0 < desired_error_prob && desired_error_prob < 1.0);
GrowableBloom {
blooms: vec![],
desired_error_prob,
est_insertions,
inserts: 0,
capacity: 0,
}
}
pub fn contains<T: Hash>(&self, item: T) -> bool {
let (h1, h2) = double_hashing_hashes(item);
self.blooms.iter().any(|bloom| bloom.contains(h1, h2))
}
pub fn insert<T: Hash>(&mut self, item: T) -> bool {
let (h1, h2) = double_hashing_hashes(item);
if self.blooms.iter().any(|bloom| bloom.contains(h1, h2)) {
return false;
}
if self.inserts >= self.capacity {
self.grow();
}
self.inserts += 1;
let curr_bloom = self.blooms.last_mut().unwrap();
curr_bloom.insert(h1, h2);
true
}
pub fn clear(&mut self) {
self.blooms.clear();
self.inserts = 0;
self.capacity = 0;
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inserts == 0
}
#[inline]
pub fn len(&self) -> usize {
self.inserts
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn check_and_set<T: Hash>(&mut self, item: T) -> bool {
!self.insert(item)
}
fn grow(&mut self) {
let error_ratio =
self.desired_error_prob * Self::TIGHTENING_RATIO.powi(self.blooms.len() as _);
let capacity = self.est_insertions * Self::GROWTH_FACTOR.pow(self.blooms.len() as _);
let new_bloom = Bloom::new(capacity, error_ratio);
self.blooms.push(new_bloom);
self.capacity += capacity;
}
}
#[cfg(test)]
mod growable_bloom_tests {
mod test_bloom {
use crate::{double_hashing_hashes, Bloom};
#[test]
fn can_insert_bloom() {
let mut b = Bloom::new(100, 0.01);
let (h1, h2) = double_hashing_hashes(123);
b.insert(h1, h2);
assert!(b.contains(h1, h2))
}
#[test]
fn can_insert_string_bloom() {
let mut b = Bloom::new(100, 0.01);
let (h1, h2) = double_hashing_hashes("hello world".to_string());
b.insert(h1, h2);
assert!(b.contains(h1, h2))
}
#[test]
fn does_not_contain() {
let mut b = Bloom::new(100, 0.01);
let upper = 100;
for i in (0..upper).step_by(2) {
let (h1, h2) = double_hashing_hashes(i);
b.insert(h1, h2);
assert!(b.contains(h1, h2))
}
for i in (1..upper).step_by(2) {
let (h1, h2) = double_hashing_hashes(i);
assert!(!b.contains(h1, h2))
}
}
#[test]
fn can_insert_lots() {
let mut b = Bloom::new(100, 0.01);
for i in 0..1024 {
let (h1, h2) = double_hashing_hashes(i);
b.insert(h1, h2);
assert!(b.contains(h1, h2))
}
}
#[test]
fn test_refs() {
let item = String::from("Hello World");
let mut b = Bloom::new(100, 0.01);
let (h1, h2) = double_hashing_hashes(&item);
b.insert(h1, h2);
assert!(b.contains(h1, h2))
}
}
mod test_growable {
use crate::GrowableBloom;
use serde_json;
#[test]
fn can_insert() {
let mut b = GrowableBloom::new(0.05, 1000);
let item = 20;
b.insert(&item);
assert!(b.contains(&item))
}
#[test]
fn len_capacity_clear() {
let mut b = GrowableBloom::new(0.05, 100);
assert_eq!(b.len(), 0);
assert_eq!(b.capacity(), 0);
let item = 20;
b.insert(&item);
assert_ne!(b.len(), 0);
assert_ne!(b.capacity(), 0);
b.clear();
assert_eq!(b.len(), 0);
assert_eq!(b.capacity(), 0);
}
#[test]
fn ensure_capacity() {
let mut b = GrowableBloom::new(0.05, 1);
assert_eq!(b.capacity(), 0);
b.insert("abc");
assert_eq!(b.capacity(), 1);
for i in 0..100 {
b.insert(i);
}
assert_eq!(b.capacity(), 127);
}
#[test]
fn can_insert_string() {
let mut b = GrowableBloom::new(0.05, 1000);
let item: String = "hello world".to_owned();
b.insert(&item);
assert!(b.contains(&item))
}
#[test]
fn does_not_contain() {
let mut b = GrowableBloom::new(0.05, 1000);
assert_eq!(b.contains(&"hello"), false);
b.insert(&0);
assert_eq!(b.contains(&"hello"), false);
b.insert(&1);
assert_eq!(b.contains(&"hello"), false);
b.insert(&2);
assert_eq!(b.contains(&"hello"), false);
}
#[test]
fn can_insert_a_lot_of_elements() {
let mut b = GrowableBloom::new(0.05, 1000);
for i in 0..1000 {
b.insert(&i);
assert!(b.contains(&i));
}
}
#[test]
fn can_serialize_deserialize() {
let mut b = GrowableBloom::new(0.05, 1000);
b.insert(&0);
let s = serde_json::to_string(&b).unwrap();
let b_s: GrowableBloom = serde_json::from_str(&s).unwrap();
assert!(b_s.contains(&0));
assert_ne!(b_s.contains(&1), true);
assert_ne!(b_s.contains(&1000), true);
}
#[test]
fn verify_saturation() {
for &fp in &[0.01, 0.001] {
let fp_ub = fp / (1.0 - GrowableBloom::TIGHTENING_RATIO);
let initial_cap = 100u64;
let growth = 1000u64;
let mut b = GrowableBloom::new(fp, initial_cap as usize);
for i in 1u64..=initial_cap * growth {
b.insert(&i);
if i % (initial_cap * growth / 10) == 0
|| [1, 2, 5, 10, 25].iter().any(|&g| i == initial_cap * g)
{
let est_fp_rate = (i + 1..).take(50_000).filter(|i| b.contains(i)).count()
as f64
/ 50_000.0;
assert!(est_fp_rate <= fp_ub);
}
}
for i in 1u64..=initial_cap * growth {
assert!(b.contains(&i));
}
}
}
#[test]
fn test_types_saturation() {
let mut b = GrowableBloom::new(0.50, 100);
b.insert(&vec![1, 2, 3]);
b.insert("hello");
b.insert(&-1);
b.insert(&0);
}
#[test]
fn can_check_and_set() {
let mut b = GrowableBloom::new(0.05, 1000);
let item = 20;
assert!(!b.check_and_set(&item));
assert!(b.check_and_set(&item));
}
}
#[cfg(feature = "nightly")]
mod bench {
use crate::GrowableBloom;
use test::Bencher;
#[bench]
fn bench_new(b: &mut Bencher) {
b.iter(|| GrowableBloom::new(0.01, 1000));
}
#[bench]
fn bench_insert_normal_prob(b: &mut Bencher) {
let mut gbloom = GrowableBloom::new(0.01, 1000);
b.iter(|| gbloom.insert(10));
}
#[bench]
fn bench_insert_small_prob(b: &mut Bencher) {
let mut gbloom = GrowableBloom::new(0.001, 1000);
b.iter(|| gbloom.insert(10));
}
#[bench]
fn bench_many(b: &mut Bencher) {
let mut gbloom = GrowableBloom::new(0.01, 100000);
b.iter(|| gbloom.insert(10));
}
#[bench]
fn bench_insert_medium(b: &mut Bencher) {
let s: String = (0..100).map(|_| 'X').collect();
let mut gbloom = GrowableBloom::new(0.01, 100000);
b.iter(|| gbloom.insert(&s))
}
#[bench]
fn bench_insert_large(b: &mut Bencher) {
let s: String = (0..10000).map(|_| 'X').collect();
let mut gbloom = GrowableBloom::new(0.01, 100000);
b.iter(|| gbloom.insert(&s))
}
#[bench]
fn bench_insert_large_very_small_prob(b: &mut Bencher) {
let s: String = (0..10000).map(|_| 'X').collect();
let mut gbloom = GrowableBloom::new(0.0001, 100000);
b.iter(|| gbloom.insert(&s))
}
#[bench]
fn bench_grow(b: &mut Bencher) {
b.iter(|| {
let mut gbloom = GrowableBloom::new(0.01, 100);
for i in 0..1000 {
gbloom.insert(&i);
assert!(gbloom.contains(&i));
}
})
}
}
}