diff --git a/Cargo.toml b/Cargo.toml index f3700d6..c2a6f54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,15 +13,18 @@ exclude = ["fuzz"] rust-version = "1.71" [features] -default = ["ahash", "parking_lot"] +default = ["custom-hasher", "parking_lot"] +# Use the crate's bundled fast default hasher (currently foldhash) behind the +# opaque `DefaultHashBuilder`. The concrete hasher is an implementation detail. +custom-hasher = ["dep:foldhash"] sharded-lock = ["dep:crossbeam-utils"] shuttle = ["dep:shuttle"] stats = [] [dependencies] -ahash = { optional = true, version = "0.8" } crossbeam-utils = { optional = true, version = "0.8" } equivalent = "1.0" +foldhash = { optional = true, version = "0.2" } hashbrown = { version = "0.16", default-features = false, features = ["inline-more"] } parking_lot = { optional = true, version = "0.12" } shuttle = { version = "0.8", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 4db978a..ff096de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,9 +59,11 @@ //! //! # Hasher //! -//! By default the crate uses [ahash](https://crates.io/crates/ahash), which is enabled (by default) via -//! a crate feature with the same name. If the `ahash` feature is disabled the crate defaults to the std lib -//! implementation instead (currently Siphash13). Note that a custom hasher can also be provided if desirable. +//! By default the crate uses a fast non-cryptographic hasher (currently [foldhash](https://crates.io/crates/foldhash)) +//! exposed through the opaque [`DefaultHashBuilder`] type and enabled via the `custom-hasher` feature (on by +//! default). The concrete algorithm is an implementation detail and may change between releases. If the +//! `custom-hasher` feature is disabled the crate falls back to the std lib implementation instead (currently +//! Siphash13). Note that a custom hasher can also be provided via the cache's `B` type parameter if desirable. //! //! # Synchronization primitives //! @@ -75,7 +77,7 @@ //! //! | Feature | Default | Description | //! |---------|---------|-------------| -//! | `ahash` | ✓ | Use [ahash](https://crates.io/crates/ahash) as the default hasher. When disabled, falls back to std lib's `RandomState` (currently SipHash-1-3). | +//! | `custom-hasher` | ✓ | Use the crate's bundled fast non-cryptographic default hasher (currently [foldhash](https://crates.io/crates/foldhash)) behind the opaque [`DefaultHashBuilder`]. When disabled, falls back to std lib's `RandomState` (currently SipHash-1-3). | //! | `parking_lot` | ✓ | Use [parking_lot](https://crates.io/crates/parking_lot) for synchronization primitives. Mutually exclusive with `sharded-lock`. | //! | `sharded-lock` | | Use [`crossbeam_utils::sync::ShardedLock`](https://docs.rs/crossbeam-utils/latest/crossbeam_utils/sync/struct.ShardedLock.html) for synchronization primitives. Mutually exclusive with `parking_lot`. | //! | `shuttle` | | Enable [shuttle](https://crates.io/crates/shuttle) testing support for concurrency testing. | @@ -107,10 +109,35 @@ mod shuttle_tests; pub use options::{Options, OptionsBuilder}; -#[cfg(feature = "ahash")] -pub type DefaultHashBuilder = ahash::RandomState; -#[cfg(not(feature = "ahash"))] -pub type DefaultHashBuilder = std::collections::hash_map::RandomState; +#[cfg(feature = "custom-hasher")] +type DefaultHasherImpl = foldhash::quality::RandomState; +#[cfg(not(feature = "custom-hasher"))] +type DefaultHasherImpl = std::collections::hash_map::RandomState; + +/// The default [`BuildHasher`](std::hash::BuildHasher) used by the cache. +/// +/// The hashing algorithm behind this type is an implementation detail and may +/// change in any release. Name this type — don't rely on the concrete hasher +/// it wraps. The underlying hasher is selected by the `custom-hasher` feature +/// (a fast non-cryptographic hasher when enabled, the std library's +/// `RandomState` otherwise). +#[derive(Clone, Default)] +pub struct DefaultHashBuilder(DefaultHasherImpl); + +impl std::hash::BuildHasher for DefaultHashBuilder { + type Hasher = ::Hasher; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + self.0.build_hasher() + } +} + +impl std::fmt::Debug for DefaultHashBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DefaultHashBuilder").finish_non_exhaustive() + } +} /// Defines the weight of a cache entry. /// diff --git a/src/sync.rs b/src/sync.rs index 363fde0..9683ce7 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -229,6 +229,12 @@ impl< // shard, rotate the hash by usize::BITS / 2 so we avoid the lower bits and // the highest 7 bits that hashbrown uses internally for probing, improving // the real entropy available to each hashbrown shard. + // + // The rotation is deliberately tied to usize::BITS, not u64::BITS: on 32-bit + // targets a usize-width hasher (e.g. FxHash) leaves the top 32 hash bits zero + // (hashbrown guards against this too; see its h1 / Tag::full), so we keep the + // shard window in the low word where the entropy actually lives. Rotating by + // u64::BITS / 2 there would send every key to shard 0. hash.rotate_right(usize::BITS / 2) & self.shards_mask }