BloomCraft
A production-grade Bloom filter library for Rust. BloomCraft provides twelve filter variants, from the classical space-optimal filter to scalable, partitioned, register-blocked, and concurrent implementations, unified under a coherent trait hierarchy with type-state builders, pluggable hash strategies, and optional Serde, metrics, and SIMD support.
Why BloomCraft?
BloomCraft ships twelve, covering every practical trade-off between space, speed, deletion, scalability, and concurrency, all under a single, coherent API featuring:
- Three distinct concurrency models:
&mut selfwith external locking,&selflock-free operations viaAtomicU64CAS, and&selfwait-free operations via interior mutability. - Type-state builders: Misconfiguration is a compile-time error, not a runtime panic.
- Pluggable hash strategies: From standard SipHash to SIMD-accelerated WyHash and XXH3.
- Audited unsafe internals: The public API stays safe; any internal
unsafeis tightly scoped, documented, and reviewed.
If you need a filter you can delete from, one that grows without bounds, one that saturates a single cache line per query, or one that accepts concurrent writes from 64 threads without a Mutex in sight, this crate provides a specific, mathematically-verified type for your requirement rather than bolting synchronization onto a generic struct.
Table of Contents
- Installation
- Quick Start
- Filter Selection
- Filter Variants
- Concurrency Models
- Type-State Builders
- Hash Strategies
- Feature Flags
- Architecture
- Benchmarks
- References
- Contact
- Contributing
- License
Installation
[]
= "0.1"
With optional features:
[]
= { = "0.1", = ["serde", "wyhash", "metrics", "concurrent"] }
Minimum Supported Rust Version (MSRV): 1.73
Quick Start
use BloomFilter;
use StandardBloomFilter;
Filter Selection
| Use case | Filter | Feature gate | Notes |
|---|---|---|---|
| General purpose, known capacity | StandardBloomFilter |
always | Space-optimal, supports union/intersect |
| Need deletion | CountingBloomFilter |
always | 4-16x memory overhead, per-element counters |
| Unknown or growing dataset | ScalableBloomFilter |
always | Auto-grows, bounded compound FPR |
| Concurrent, growing dataset | AtomicScalableBloomFilter |
concurrent |
Sharded internals, CAS-based growth |
| Query-heavy, cache-sensitive | PartitionedBloomFilter |
always | Partitioned bit array, cache-aligned |
| High throughput, FPR-tolerant | RegisterBlockedBloomFilter |
always | 512-bit register blocks, one cache-line touch per query |
| Concurrent, cache-optimized | AtomicPartitionedBloomFilter |
concurrent |
Atomic partitioned filter |
| Location-aware queries | TreeBloomFilter |
always | Hierarchical bins, returns matching subtree |
| High-concurrency writes | ShardedBloomFilter |
always | &self insert via atomic shards |
| High-concurrency, low memory | StripedBloomFilter |
always | Striped RwLock, &self |
| Educational baseline | ClassicHashFilter |
always | Bloom (1970) Method 1 |
| Educational baseline | ClassicBitsFilter |
always | Bloom (1970) Method 2 |
Concurrency quick-reference
| Filter | Insert requires | Mechanism |
|---|---|---|
StandardBloomFilter |
&mut self or &self |
Atomic CAS on AtomicU64 |
CountingBloomFilter |
&mut self |
External Mutex |
ScalableBloomFilter |
&mut self |
External Mutex |
AtomicScalableBloomFilter |
&self |
Shards + RwLock for growth |
PartitionedBloomFilter |
&mut self |
External RwLock |
RegisterBlockedBloomFilter |
&mut self |
External Mutex |
AtomicPartitionedBloomFilter |
&self |
AtomicU64 fetch_or |
TreeBloomFilter |
&mut self |
External RwLock |
ShardedBloomFilter |
&self |
Atomic shards |
StripedBloomFilter |
&self |
Striped RwLock array |
Filter Variants
StandardBloomFilter
Classic space-optimal Bloom filter backed by AtomicU64 words. Supports
&self concurrent writes via the ConcurrentBloomFilter extension trait.
use ;
use StandardBloomFilter;
let mut filter_a = new?;
let mut filter_b = new?;
filter_a.insert;
println!;
println!;
println!;
let union = filter_a.union?;
CountingBloomFilter
Extends the standard filter with per-slot counters for safe deletion. Counter width is configurable to 4, 8, or 16 bits per slot.
use ;
use CountingBloomFilter;
let mut filter = new;
let item = "item".to_string;
filter.insert;
assert!;
let removed = filter.delete;
assert!;
assert!;
println!;
ScalableBloomFilter
Maintains a chain of fixed-size filter slices. When the active slice exceeds
fill_threshold (default 0.5), a new slice is appended with scaled capacity
and tightened FPR. The compound FPR across all slices remains bounded.
use BloomFilter;
use ;
let mut filter = with_strategy?;
for i in 0..50_000_u64
let metrics = filter.health_metrics;
println!;
println!;
PartitionedBloomFilter
Divides the bit array into k equal partitions. Each hash function probes
within one partition, keeping memory access local to a single cache line.
use BloomFilter;
use PartitionedBloomFilter;
let mut filter = with_alignment?;
filter.insert;
println!;
RegisterBlockedBloomFilter
Uses 512-bit register blocks so each query touches exactly one cache line.
Throughput is higher than StandardBloomFilter at the cost of a higher FPR
for a given memory budget (the block-aligned layout wastes bits at block
boundaries).
use BloomFilter;
use RegisterBlockedBloomFilter;
let mut filter = new?;
filter.insert;
assert!;
TreeBloomFilter
A hierarchical filter that assigns items to leaf bins in a branching tree. Useful for location-aware lookups across tiered storage (e.g., region to datacenter to rack).
use BloomFilter;
use TreeBloomFilter;
let mut router = new?;
let item = "session:alice".to_string;
router.insert_to_bin;
assert!;
for path in router.locate
AtomicScalableBloomFilter (feature: concurrent)
Concurrent variant of ScalableBloomFilter using sharded sub-filters and
CAS-based growth election. All operations take &self.
use AtomicScalableBloomFilter;
use Arc;
let filter = new;
let f = clone;
spawn;
AtomicPartitionedBloomFilter (feature: concurrent)
Concurrent partitioned filter using AtomicU64 for lock-free bit operations
on cache-line-aligned partitions.
use AtomicPartitionedBloomFilter;
use Arc;
let filter = new;
let f = clone;
spawn;
ShardedBloomFilter
Distributes items across independent StandardBloomFilter shards. Each shard
is lock-free; shards are selected by hash. Good for high-write-throughput
workloads.
use SharedBloomFilter;
use ShardedBloomFilter;
use Arc;
let filter = new;
let f = clone;
spawn;
filter.insert;
assert!;
println!;
StripedBloomFilter
A single logical filter striped into RwLock-protected regions. Provides
&self operations with finer-grained locking than a single Mutex.
use SharedBloomFilter;
use StripedBloomFilter;
use Arc;
let filter = new;
let f = clone;
spawn;
filter.insert;
assert!;
println!;
ClassicBitsFilter / ClassicHashFilter
Implementations of Bloom's original 1970 paper. Method 1 (ClassicBitsFilter)
and Method 2 (ClassicHashFilter). Provided as educational baselines and
research references. Not recommended for production use.
use BloomFilter;
use ClassicBitsFilter;
let mut filter = with_fpr;
filter.insert;
assert!;
assert!;
println!;
use BloomFilter;
use ClassicHashFilter;
let mut filter = with_fpr;
let item = "hello".to_string;
filter.insert;
assert!;
println!;
Concurrency Models
BloomCraft provides three synchronization models, distinguished by the insert
method's self type.
1. Single-threaded / external lock (&mut self)
Standard Rust ownership. Wrap in Arc<Mutex<T>> for multi-threaded access.
Applies to CountingBloomFilter, PartitionedBloomFilter, etc.
2. Atomic operations (&self via ConcurrentBloomFilter)
Uses atomic fetch_or with Ordering::Relaxed on AtomicU64 words. Applies
to StandardBloomFilter (which implements ConcurrentBloomFilter).
use ConcurrentBloomFilter;
use StandardBloomFilter;
use Arc;
let filter = new;
let f = clone;
spawn;
3. Interior mutability (&self via SharedBloomFilter)
Concurrency managed within the type using atomic shards (ShardedBloomFilter)
or padded RwLock striping (StripedBloomFilter).
Type-State Builders
Builders enforce correct parameter ordering at compile time, eliminating runtime panics from missing or misordered configuration.
use StandardBloomFilterBuilder;
use HashStrategy;
let = new
.expected_items
.false_positive_rate
.hash_strategy
.?;
println!;
Hash Strategies
All filters use Lemire's unbiased range reduction. The hash strategies map
two 64-bit seeds to k indices:
| Strategy | Formula | Notes |
|---|---|---|
Double |
h(i) = h1 + i * h2 |
Kirsch-Mitzenmacher (2006) |
EnhancedDouble (default) |
h(i) = h1 + i * h2 + (i^2 + i) / 2 |
Better uniformity |
Triple |
h(i) = h1 + i * h2 + i^2 * h3 |
Maximum independence |
Underlying hashers are configured via feature flags:
| Hasher | Feature | Algorithm |
|---|---|---|
StdHasher |
(default) | SipHash-1-3 |
WyHasher |
wyhash |
WyHash |
XxHasher |
xxhash |
XXH3 |
Feature Flags
| Flag | Description |
|---|---|
serde |
Serialize/Deserialize for all filter types, plus zero-copy binary format |
bincode |
Bincode encoding (implies serde) |
xxhash |
XxHasher (XXH3) |
wyhash |
WyHasher |
rayon |
Parallel batch insert / query |
simd |
AVX2 / SSE4.1 / NEON vectorized batch hashing |
metrics |
MetricsCollector, FalsePositiveTracker, LatencyHistogram |
trace |
Per-query QueryTrace timing instrumentation |
concurrent |
AtomicScalableBloomFilter, AtomicPartitionedBloomFilter |
proptest |
Property-based test utilities |
Architecture
src/
core/ Traits (BloomFilter, ConcurrentBloomFilter), BitVec, math
filters/ Core filter implementations
sync/ ShardedBloomFilter, StripedBloomFilter
builder/ Type-state builders
hash/ BloomHasher trait, hash strategies, hasher impls
metrics/ Telemetry, latency histograms, FPR tracking
serde_support/ Serialization formats, zero-copy bindings
error.rs BloomCraftError enum
Benchmarks
BloomCraft includes a comprehensive cross-variant benchmark suite that
compares all 12 filter variants across throughput, scaling, concurrency,
latency, memory, and edge-case dimensions. Benchmarks use Criterion.rs
and live in benches/comparison_bench.rs.
Run the full suite:
Results below were measured on an AMD Ryzen laptop, N = 100,000 items,
1% target FPR, u64 type, --release profile.
1. Insert Throughput (single-threaded, 100K items)
| Rank | Variant | Melem/s | Notes |
|---|---|---|---|
| 1 | PartitionedBloomFilter | 79.4 | Partition-level parallelism |
| 2 | ClassicHashFilter | 65.4 | Inline element storage (fast for Copy types) |
| 3 | AtomicPartitionedBloomFilter | 46.9 | Same layout + atomic ops |
| 4 | RegisterBlockedBloomFilter | 37.9 | Block-SIMD, one cache-line touch per query |
| 5 | StandardBloomFilter | 20.8 | AtomicU64 CAS, good general purpose |
| 6 | ScalableBloomFilter | 20.3 | Growth coordination overhead |
| 7 | AtomicScalableBloomFilter | 19.0 | Growth + atomic overhead |
| 8 | ClassicBitsFilter | 18.8 | k bit-vectors |
| 9 | StripedBloomFilter | 11.1 | RwLock stripe contention |
| 10 | ShardedBloomFilter | 10.6 | Shard-selection hash overhead |
| 11 | CountingBloomFilter | 9.1 | 4-bit counter maintenance |
| 12 | TreeBloomFilter | 5.2 | Recursive tree descent |
2. Contains Throughput (single-threaded, 50/50 hit/miss)
| Rank | Variant | Melem/s | Notes |
|---|---|---|---|
| 1 | ClassicHashFilter | 144.9 | Direct == compare on u64 |
| 2 | PartitionedBloomFilter | 68.8 | Partition-level early exit |
| 3 | AtomicPartitionedBloomFilter | 67.8 | Same layout |
| 4 | RegisterBlockedBloomFilter | 47.2 | One cache-line touch |
| 5 | StandardBloomFilter | 27.4 | k hash functions, k bit reads |
| 6 | TreeBloomFilter | 23.1 | Tree descent |
| 7 | ClassicBitsFilter | 21.5 | Simple bit check |
| 8 | ScalableBloomFilter | 13.9 | Scans sub-filters |
| 9 | CountingBloomFilter | 13.5 | Counter check |
| 10 | AtomicScalableBloomFilter | 13.0 | Sub-filter scan |
| 11 | ShardedBloomFilter | 11.6 | Shard dispatch |
| 12 | StripedBloomFilter | 11.5 | RwLock read-lock per query |
Note: ClassicHashFilter's advantage is specific to
Copytypes. ForStringits throughput drops ~6× (see dictionary benchmark).
3. FPR Accuracy (empirical vs 1% target)
All variants match the target FPR within tolerance. ScalableBloomFilter is deliberately conservative (tight compound bound on sub-filters):
| Variant | Empirical FPR |
|---|---|
| StandardBloomFilter | 1.00% |
| CountingBloomFilter | 1.06% |
| ScalableBloomFilter | 0.02% |
| PartitionedBloomFilter | 1.02% |
| RegisterBlockedBloomFilter | ~1.00% |
| TreeBloomFilter | ~0.58% |
| ClassicBitsFilter | ~1.00% |
| ClassicHashFilter | ~1.07% |
| ShardedBloomFilter | 0.99% |
| StripedBloomFilter | ~1.00% |
| Atomic variants | ~1.00% |
4. Input Scaling (StandardBloomFilter)
Insert throughput decreases with set size as cache pressure grows:
| N | Insert (Melem/s) | Contains (Melem/s) |
|---|---|---|
| 1,000 | 24.2 | 31.2 |
| 10,000 | 24.0 | 29.1 |
| 100,000 | 20.9 | 27.1 |
| 1,000,000 | 17.4 | 20.2 |
PartitionedBloomFilter scales better, staying above 55 Melem/s even at 1M items due to its L1-cache-friendly partition structure:
| N | Partitioned Insert (Melem/s) | ClassicHash Insert (Melem/s) |
|---|---|---|
| 1,000 | 81.0 | 63.3 |
| 10,000 | 79.7 | 65.0 |
| 100,000 | 77.8 | 64.9 |
| 1,000,000 | 55.1 | 64.4 |
5. Concurrent Thread Scaling (insert throughput, Melem/s)
| Threads | Standard | Sharded | Striped | AtomicPartitioned | AtomicScalable |
|---|---|---|---|---|---|
| 1 | 20.0 | 10.6 | 10.7 | 35.3 | 15.6 |
| 2 | 26.7 | 13.3 | 8.8 | 19.5 | 9.1 |
| 4 | 43.6 | 20.7 | 8.8 | 27.7 | 8.9 |
| 8 | 62.1 | 26.9 | 8.3 | 32.4 | 11.0 |
| 16 | 59.6 | 27.2 | 8.2 | 31.6 | 11.6 |
StandardBloomFilter scales 3.1× from 1→8 threads (lock-free AtomicU64
CAS). ShardedBloomFilter scales 2.5×. StripedBloomFilter does not
scale — RwLock stripe contention on this machine. AtomicPartitioned
hits the documented anti-pattern: 2 threads slower than 1 due to cache-line
ping-pong on shared atomic words.
6. Read/Write Mix (8 threads, total Ops/s via Melem/s)
| Ratio | Standard | Sharded | Striped | AtomicScalable | AtomicPartitioned |
|---|---|---|---|---|---|
| 10% write | 95.3 | 32.5 | — | 12.9 | 82.5 |
| 50% write | 81.1 | 30.5 | — | 12.1 | 41.1 |
| 90% write | 77.7 | 30.3 | — | 10.4 | 30.4 |
StandardBloomFilter excels at read-heavy workloads under concurrent access. AtomicPartitioned benefits from read-only (atomic load) but degrades sharply as writes increase (cache-line contention).
7. Batch Size Sensitivity
Batch insertion throughput for StandardBloomFilter as chunk size varies (N = 100K):
| Batch Size | Insert (Melem/s) |
|---|---|
| 1 | 10.6 |
| 10 | 18.9 |
| 100 | 20.2 |
| 1,000 | 20.7 |
| 10,000 | 20.7 |
Throughput saturates around batch size 100. Below that, per-call overhead dominates. PartitionedBloomFilter shows a wider spread (17.1 → 66.7 Melem/s) because it benefits more from amortizing partition dispatch.
8. FPR Under Overfill
When the filter is filled beyond its designed capacity, fixed-size filters degrade identically; ScalableBloomFilter maintains its FPR by growing internally:
| Fill | Standard | Scalable | Partitioned | Sharded | Counting |
|---|---|---|---|---|---|
| 100% | 1.00% | 0.02% | 1.02% | 0.99% | 1.06% |
| 150% | 5.81% | 0.04% | 5.80% | 5.76% | 5.85% |
| 200% | 15.83% | 0.04% | 15.85% | 15.75% | 15.81% |
9. String Dictionary (English words, N = 100K)
Using a dictionary of 250 common English words instead of random u64:
| Variant | String (Melem/s) | u64 (Melem/s) | Slowdown |
|---|---|---|---|
| ClassicHashFilter | 45.4 | 65.4 | 1.4× |
| AtomicPartitionedBloomFilter | 43.0 | 46.9 | 1.1× |
| StandardBloomFilter | 19.9 | 20.8 | 1.0× |
| ScalableBloomFilter | 17.7 | 20.3 | 1.1× |
| AtomicScalableBloomFilter | 14.5 | 19.0 | 1.3× |
| CountingBloomFilter | 11.1 | 9.1 | 0.8× |
| ClassicBitsFilter | 11.0 | 18.8 | 1.7× |
| StripedBloomFilter | 10.3 | 11.1 | 1.1× |
| ShardedBloomFilter | 9.7 | 10.6 | 1.1× |
| TreeBloomFilter | 5.0 | 5.2 | 1.0× |
ClassicHashFilter remains fast because dictionary words are short
(<10 chars), keeping memcmp cheap. PartitionedBloomFilter's
advantage over Standard shrinks because the hashing cost becomes
a smaller fraction of total insertion cost.
10. Concurrent Contains Tail Latency (8 threads, 40K queries)
| Variant | p50 | p95 | p99 | p99.9 |
|---|---|---|---|---|
| StandardBloomFilter | 200 ns | 300 ns | 400 ns | 12.3 µs |
| ShardedBloomFilter | 600 ns | 1.4 µs | 2.4 µs | 19.1 µs |
| StripedBloomFilter | 700 ns | 1.5 µs | 2.5 µs | 19.1 µs |
StandardBloomFilter's atomic read path has the lowest and most consistent latency. Sharded and Striped show higher tail latency due to dispatch overhead and lock contention.
11. Memory Footprint (at N = 100K, 1% target FPR)
| Variant | Bits | MB | Notes |
|---|---|---|---|
| Standard / Partitioned / Striped | ~0.96M | 0.11 | Optimal ~9.6 bits/item |
| ShardedBloomFilter | ~0.96M | 0.11 | Same as Standard per shard |
| TreeBloomFilter | ~0.96M | 0.11 | Branching [10, 10] |
| RegisterBlockedBloomFilter | ~0.96M | 0.11 | Block-level 4-bit counters |
| ScalableBloomFilter | ~2.10M | 0.25 | Growth headroom |
| AtomicScalableBloomFilter | ~3.16M | 0.38 | Growth + atomic overhead |
| CountingBloomFilter | ~3.92M | 0.47 | 4× bit cost for counters |
| ClassicBitsFilter | ~0.98M | 0.12 | k separate bit-vectors |
| ClassicHashFilter | ~1.47G | 175.78 | Stores actual elements, not bits |
ClassicHashFilter's storage grows with element size — it stores the elements themselves rather than a bit signature, making it unsuitable for large or heap-allocated types.
Running Individual Benchmarks
Per-variant benchmarks live alongside the comparison suite:
Memory efficiency reference
For a standard Bloom filter, the optimal bit count per item is
m = -n * ln(p) / (ln 2)^2:
| Target FPR | Bits per element | Memory for 1,000,000 items |
|---|---|---|
| 10% | ~4.8 | ~600 KB |
| 1% | ~9.6 | ~1.2 MB |
| 0.1% | ~14.4 | ~1.8 MB |
| 0.01% | ~19.2 | ~2.4 MB |
Note: RegisterBlockedBloomFilter and partitioned variants deviate from
optimal memory due to alignment constraints. The actual FPR for a given
capacity is slightly higher than the target. Run cargo bench --bench register_blocked_bench -- rbbf/fpr_targets to measure the gap.
References
The academic papers that informed BloomCraft's design are listed below and are also preserved in the references/ directory for convenient browsing.
- Bloom, B. H. (1970). Space/time trade-offs in hash coding with allowable errors. Communications of the ACM.
- Kirsch, A. & Mitzenmacher, M. (2006). Less Hashing, Same Performance: Building a Better Bloom Filter. ESA.
- Almeida, P. et al. (2007). Scalable Bloom Filters. Information Processing Letters.
- Lemire, D. (2019). Fast Random Integer Generation in an Interval. ACM Transactions on Modeling and Computer Simulation.
Contact
- Security reports: zaudrehman@gmail.com
- General contribution questions: open a GitHub issue
Contributing
Bug reports, API feedback, and pull requests are welcome.
- Issues: Label as
bug,enhancement, orquestion. - Pull requests: Target
main. Include documentation (///), tests, and aCHANGELOG.mdentry. - Unsafe code: The library uses
unsafein limited, audited locations (primarily SIMD intrinsics, manual allocation inPartitionedBloomFilter, andSend/Syncimpls for concurrent types). Pull requests introducing newunsafemust include a safety comment explaining invariants and preconditions. - MSRV: Do not use features stabilized after Rust 1.73 without prior coordination.
License
Licensed under either of MIT License or Apache License, Version 2.0 at your option.