pub mod hasher;
pub mod strategies;
#[cfg(feature = "wyhash")]
pub mod wyhash;
#[cfg(feature = "xxhash")]
pub mod xxhash;
#[cfg(feature = "simd")]
pub mod simd;
pub use hasher::{BloomHasher, StdHasher};
pub use strategies::{DoubleHashing, EnhancedDoubleHashing, HashStrategy, TripleHashing};
#[cfg(feature = "wyhash")]
pub use wyhash::{WyHasher, WyHasherBuilder};
#[cfg(feature = "xxhash")]
pub use xxhash::{XxHasher, XxHasherBuilder};
#[cfg(feature = "simd")]
pub use simd::SimdHasher;
pub use strategies::HashStrategyKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum IndexingStrategy {
Double,
#[default]
EnhancedDouble,
Triple,
}
impl IndexingStrategy {
#[must_use]
pub const fn base_hash_count(&self) -> usize {
match self {
Self::Double | Self::EnhancedDouble => 2,
Self::Triple => 3,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Double => "Double",
Self::EnhancedDouble => "EnhancedDouble",
Self::Triple => "Triple",
}
}
#[must_use]
pub fn generate_indices(&self, h1: u64, h2: u64, h3: u64, k: usize, m: usize) -> Vec<usize> {
match self {
Self::Double => DoubleHashing.generate_indices(h1, h2, h3, k, m),
Self::EnhancedDouble => EnhancedDoubleHashing.generate_indices(h1, h2, h3, k, m),
Self::Triple => TripleHashing.generate_indices(h1, h2, h3, k, m),
}
}
}
#[must_use]
pub fn recommended_hasher() -> impl BloomHasher {
#[cfg(feature = "wyhash")]
{
WyHasher::new()
}
#[cfg(all(not(feature = "wyhash"), feature = "xxhash"))]
{
XxHasher::new()
}
#[cfg(not(any(feature = "wyhash", feature = "xxhash")))]
{
StdHasher::new()
}
}
#[must_use]
#[cfg(feature = "simd")]
pub fn simd_hasher() -> SimdHasher {
SimdHasher::new()
}
#[must_use]
#[cfg(not(feature = "simd"))]
pub fn simd_hasher() -> impl BloomHasher {
recommended_hasher()
}
#[must_use]
pub fn hasher_with_seed(seed: u64) -> impl BloomHasher {
#[cfg(feature = "wyhash")]
{
WyHasher::with_seed(seed)
}
#[cfg(all(not(feature = "wyhash"), feature = "xxhash"))]
{
XxHasher::with_seed(seed)
}
#[cfg(not(any(feature = "wyhash", feature = "xxhash")))]
{
StdHasher::with_seed(seed)
}
}
pub mod prelude {
pub use super::hasher::{BloomHasher, StdHasher};
pub use super::strategies::{
DoubleHashing, EnhancedDoubleHashing, HashStrategy, TripleHashing,
};
pub use super::IndexingStrategy;
#[cfg(feature = "wyhash")]
pub use super::wyhash::WyHasher;
#[cfg(feature = "xxhash")]
pub use super::xxhash::XxHasher;
#[cfg(feature = "simd")]
pub use super::simd::SimdHasher;
}
pub mod bench {
use super::*;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct HashBenchmark {
pub name: String,
pub time_per_hash_ns: f64,
pub throughput: f64,
pub items_hashed: usize,
pub duration: Duration,
}
impl HashBenchmark {
pub(crate) fn new(name: String, items_hashed: usize, duration: Duration) -> Self {
let time_per_hash_ns = duration.as_nanos() as f64 / items_hashed as f64;
let throughput = items_hashed as f64 / duration.as_secs_f64();
Self {
name,
time_per_hash_ns,
throughput,
items_hashed,
duration,
}
}
}
pub fn benchmark_hasher<H: BloomHasher>(
hasher: &H,
items: &[&[u8]],
name: &str,
) -> HashBenchmark {
let start = Instant::now();
for &item in items {
let _ = std::hint::black_box(hasher.hash_bytes(item));
}
HashBenchmark::new(name.to_string(), items.len(), start.elapsed())
}
pub fn compare_hashers(items: &[&[u8]]) -> Vec<HashBenchmark> {
let mut results = vec![benchmark_hasher(&StdHasher::new(), items, "StdHasher")];
#[cfg(feature = "wyhash")]
results.push(benchmark_hasher(&WyHasher::new(), items, "WyHasher"));
#[cfg(feature = "xxhash")]
results.push(benchmark_hasher(&XxHasher::new(), items, "XxHasher"));
#[cfg(feature = "simd")]
results.push(benchmark_hasher(&SimdHasher::new(), items, "SimdHasher"));
results.sort_by(|a, b| a.time_per_hash_ns.partial_cmp(&b.time_per_hash_ns).unwrap());
results
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::StdHasher;
#[test]
fn test_indexing_strategy_base_hash_count() {
assert_eq!(IndexingStrategy::Double.base_hash_count(), 2);
assert_eq!(IndexingStrategy::EnhancedDouble.base_hash_count(), 2);
assert_eq!(IndexingStrategy::Triple.base_hash_count(), 3);
}
#[test]
fn test_indexing_strategy_names() {
assert_eq!(IndexingStrategy::Double.name(), "Double");
assert_eq!(IndexingStrategy::EnhancedDouble.name(), "EnhancedDouble");
assert_eq!(IndexingStrategy::Triple.name(), "Triple");
}
#[test]
fn test_indexing_strategy_default_is_enhanced_double() {
assert_eq!(
IndexingStrategy::default(),
IndexingStrategy::EnhancedDouble
);
}
#[test]
fn test_indexing_strategy_generate_indices_bounds() {
for variant in [
IndexingStrategy::Double,
IndexingStrategy::EnhancedDouble,
IndexingStrategy::Triple,
] {
let indices = variant.generate_indices(12345, 67890, 11111, 7, 1000);
assert_eq!(
indices.len(),
7,
"{:?} should generate exactly k indices",
variant
);
assert!(
indices.iter().all(|&i| i < 1000),
"{:?} produced out-of-bounds index",
variant
);
}
}
#[test]
fn test_indexing_strategy_is_deterministic() {
for variant in [
IndexingStrategy::Double,
IndexingStrategy::EnhancedDouble,
IndexingStrategy::Triple,
] {
let a = variant.generate_indices(999, 888, 777, 5, 500);
let b = variant.generate_indices(999, 888, 777, 5, 500);
assert_eq!(a, b, "{:?} must be deterministic", variant);
}
}
#[test]
fn test_indexing_strategy_variants_differ() {
let (h1, h2, h3) = (
0x1111_2222_3333_4444u64,
0x5555_6666_7777_8888u64,
0x9999_aaaa_bbbb_cccc_u64,
);
let double = IndexingStrategy::Double.generate_indices(h1, h2, h3, 20, 10_000);
let enhanced = IndexingStrategy::EnhancedDouble.generate_indices(h1, h2, h3, 20, 10_000);
let triple = IndexingStrategy::Triple.generate_indices(h1, h2, h3, 20, 10_000);
assert_ne!(double, enhanced);
assert_ne!(enhanced, triple);
assert_ne!(double, triple);
}
#[test]
fn test_indexing_strategy_wrapping_safety() {
for variant in [
IndexingStrategy::Double,
IndexingStrategy::EnhancedDouble,
IndexingStrategy::Triple,
] {
let indices = variant.generate_indices(u64::MAX, u64::MAX, u64::MAX, 10, 1000);
assert_eq!(indices.len(), 10);
assert!(indices.iter().all(|&i| i < 1000));
}
}
#[test]
fn test_recommended_hasher_is_deterministic() {
let h = recommended_hasher();
assert_eq!(h.hash_bytes(b"test"), h.hash_bytes(b"test"));
}
#[test]
fn test_recommended_hasher_nonzero() {
assert_ne!(recommended_hasher().hash_bytes(b"test"), 0);
}
#[test]
fn test_simd_hasher_nonzero() {
assert_ne!(simd_hasher().hash_bytes(b"test"), 0);
}
#[test]
fn test_hasher_with_seed_independence() {
let h1 = hasher_with_seed(1).hash_bytes(b"test");
let h2 = hasher_with_seed(2).hash_bytes(b"test");
assert_ne!(h1, h2);
}
#[test]
fn test_hasher_with_seed_deterministic() {
let h1 = hasher_with_seed(42).hash_bytes(b"test");
let h2 = hasher_with_seed(42).hash_bytes(b"test");
assert_eq!(h1, h2);
}
#[test]
fn test_default_hasher_alias_is_std_hasher() {
let a: StdHasher = StdHasher::new();
let b: StdHasher = StdHasher::new();
assert_eq!(a.hash_bytes(b"alias check"), b.hash_bytes(b"alias check"));
}
#[test]
fn test_prelude_imports_compile() {
use prelude::*;
let h = StdHasher::new();
let (h1, h2) = h.hash_item(&"prelude test".to_string());
assert_ne!(h1, h2);
}
#[test]
fn test_prelude_strategy_accessible() {
use prelude::*;
let indices = DoubleHashing.generate_indices(1, 2, 0, 5, 100);
assert_eq!(indices.len(), 5);
}
#[test]
fn test_bench_benchmark_hasher_fields() {
let hasher = StdHasher::new();
let items: Vec<Vec<u8>> = (0..100)
.map(|i| format!("item{}", i).into_bytes())
.collect();
let refs: Vec<&[u8]> = items.iter().map(Vec::as_slice).collect();
let result = bench::benchmark_hasher(&hasher, &refs, "StdHasher");
assert_eq!(result.name, "StdHasher");
assert_eq!(result.items_hashed, 100);
assert!(result.time_per_hash_ns > 0.0);
assert!(result.throughput > 0.0);
}
#[test]
fn test_bench_compare_hashers_nonempty_sorted() {
let items: Vec<Vec<u8>> = (0..50).map(|i| format!("i{}", i).into_bytes()).collect();
let refs: Vec<&[u8]> = items.iter().map(Vec::as_slice).collect();
let results = bench::compare_hashers(&refs);
assert!(!results.is_empty());
for w in results.windows(2) {
assert!(
w[0].time_per_hash_ns <= w[1].time_per_hash_ns,
"Results not sorted: {} ({:.2} ns) should be ≤ {} ({:.2} ns)",
w[0].name,
w[0].time_per_hash_ns,
w[1].name,
w[1].time_per_hash_ns,
);
}
}
#[test]
#[cfg(feature = "wyhash")]
fn test_wyhash_reexport() {
let h = WyHasher::new();
assert_ne!(h.hash_bytes(b"test"), 0);
}
#[test]
#[cfg(feature = "xxhash")]
fn test_xxhash_reexport() {
let h = XxHasher::new();
assert_ne!(h.hash_bytes(b"test"), 0);
}
#[test]
#[cfg(feature = "simd")]
fn test_simd_reexport() {
let h = SimdHasher::new();
assert_ne!(h.hash_bytes(b"test"), 0);
}
#[test]
fn test_hash_item_with_indexing_strategy() {
let hasher = StdHasher::new();
let (h1, h2) = hasher.hash_item(&"end to end".to_string());
let indices = IndexingStrategy::EnhancedDouble.generate_indices(h1, h2, 0, 7, 10_000);
assert_eq!(indices.len(), 7);
assert!(indices.iter().all(|&i| i < 10_000));
}
#[test]
fn test_reexports_all_compile() {
let _ = StdHasher::new();
let _ = DoubleHashing;
let _ = EnhancedDoubleHashing;
let _ = TripleHashing;
let _ = IndexingStrategy::default();
let _ = StdHasher::new();
#[cfg(feature = "wyhash")]
{
let _ = WyHasher::new();
let _ = WyHasherBuilder::new();
}
#[cfg(feature = "xxhash")]
{
let _ = XxHasher::new();
let _ = XxHasherBuilder::new();
}
#[cfg(feature = "simd")]
{
let _ = SimdHasher::new();
}
}
}