diff --git a/Cargo.toml b/Cargo.toml index 9b19105..8dbe881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ bench = true doc = true [features] -default = ["std", "runtime-rng"] +default = ["std", "runtime-rng", "specialized-hashers"] # Enabling this will enable `AHashMap` and `AHashSet`. std = [] @@ -43,6 +43,9 @@ no-rng = [] # in case this is being used on an architecture lacking core::sync::atomic::AtomicUsize and friends atomic-polyfill = [ "dep:atomic-polyfill", "once_cell/atomic-polyfill"] +# expose specialized `AHasherU64`, `AHasherFixed`, and `AHasherStr` +specialized-hashers = [] + [[bench]] name = "ahash" path = "tests/bench.rs" diff --git a/README.md b/README.md index 18c421d..9e9b5d8 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ map.insert(56, 78); The aHash package has the following flags: * `std`: This enables features which require the standard library. (On by default) This includes providing the utility classes `AHashMap` and `AHashSet`. * `serde`: Enables `serde` support for the utility classes `AHashMap` and `AHashSet`. +* `specialized-hashers`: Exposes `AHasherU64`, `AHasherFixed`, and `AHasherStr` (as well as `BuildAHasherU64`, `BuildAHasherFixed`, and `BuildAHasherStr`), +which are specialized versions of `AHasher` for particular types of keys providing higher performance when a particular type of key is used. * `runtime-rng`: To obtain a seed for Hashers will obtain randomness from the operating system. (On by default) This is done using the [getrandom](https://github.com/rust-random/getrandom) crate. * `compile-time-rng`: For OS targets without access to a random number generator, `compile-time-rng` provides an alternative. diff --git a/src/aes_hash.rs b/src/aes_hash.rs index 702044e..e125120 100644 --- a/src/aes_hash.rs +++ b/src/aes_hash.rs @@ -213,14 +213,16 @@ impl Hasher for AHasher { } } -#[cfg(feature = "specialize")] -pub(crate) struct AHasherU64 { +/// A specialized hasher for only primitives <= 64 bits. +/// +/// Can be built with [`BuildAHasherU64`][crate::BuildAHasherU64] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] +pub struct AHasherU64 { pub(crate) buffer: u64, pub(crate) pad: u64, } -/// A specialized hasher for only primitives under 64 bits. -#[cfg(feature = "specialize")] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] impl Hasher for AHasherU64 { #[inline] fn finish(&self) -> u64 { @@ -264,11 +266,13 @@ impl Hasher for AHasherU64 { } } -#[cfg(feature = "specialize")] -pub(crate) struct AHasherFixed(pub AHasher); - /// A specialized hasher for fixed size primitives larger than 64 bits. -#[cfg(feature = "specialize")] +/// +/// Can be built with [`BuildAHasherFixed`][crate::BuildAHasherFixed] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] +pub struct AHasherFixed(pub(crate) AHasher); + +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] impl Hasher for AHasherFixed { #[inline] fn finish(&self) -> u64 { @@ -311,12 +315,15 @@ impl Hasher for AHasherFixed { } } -#[cfg(feature = "specialize")] -pub(crate) struct AHasherStr(pub AHasher); +/// A specialized hasher for strings. +/// +/// Note that other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec) +/// +/// Can be built with [`BuildAHasherStr`][crate::BuildAHasherStr] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] +pub struct AHasherStr(pub(crate) AHasher); -/// A specialized hasher for strings -/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec) -#[cfg(feature = "specialize")] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] impl Hasher for AHasherStr { #[inline] fn finish(&self) -> u64 { diff --git a/src/fallback_hash.rs b/src/fallback_hash.rs index f78074d..7f3b3a3 100644 --- a/src/fallback_hash.rs +++ b/src/fallback_hash.rs @@ -115,7 +115,7 @@ impl AHasher { } #[inline] - #[cfg(feature = "specialize")] + #[cfg(any(feature = "specialize", feature = "specialized-hashers"))] fn short_finish(&self) -> u64 { self.buffer.wrapping_add(self.pad) } @@ -199,14 +199,16 @@ impl Hasher for AHasher { } } -#[cfg(feature = "specialize")] -pub(crate) struct AHasherU64 { +/// A specialized hasher for only primitives <= 64 bits. +/// +/// Can be built with [`BuildAHasherU64`][crate::BuildAHasherU64] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] +pub struct AHasherU64 { pub(crate) buffer: u64, pub(crate) pad: u64, } -/// A specialized hasher for only primitives under 64 bits. -#[cfg(feature = "specialize")] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] impl Hasher for AHasherU64 { #[inline] fn finish(&self) -> u64 { @@ -250,11 +252,13 @@ impl Hasher for AHasherU64 { } } -#[cfg(feature = "specialize")] -pub(crate) struct AHasherFixed(pub AHasher); - /// A specialized hasher for fixed size primitives larger than 64 bits. -#[cfg(feature = "specialize")] +/// +/// Can be built with [`BuildAHasherFixed`][crate::BuildAHasherFixed] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] +pub struct AHasherFixed(pub(crate) AHasher); + +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] impl Hasher for AHasherFixed { #[inline] fn finish(&self) -> u64 { @@ -297,12 +301,15 @@ impl Hasher for AHasherFixed { } } -#[cfg(feature = "specialize")] -pub(crate) struct AHasherStr(pub AHasher); +/// A specialized hasher for strings. +/// +/// Note that other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec) +/// +/// Can be built with [`BuildAHasherStr`][crate::BuildAHasherStr] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] +pub struct AHasherStr(pub(crate) AHasher); -/// A specialized hasher for a single string -/// Note that the other types don't panic because the hash impl for String tacks on an unneeded call. (As does vec) -#[cfg(feature = "specialize")] +#[cfg(any(feature = "specialize", feature = "specialized-hashers"))] impl Hasher for AHasherStr { #[inline] fn finish(&self) -> u64 { diff --git a/src/lib.rs b/src/lib.rs index 978f424..336dc2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,12 @@ pub mod random_state; mod specialize; pub use crate::random_state::RandomState; +#[cfg(feature = "specialized-hashers")] +pub use crate::random_state::BuildAHasherU64; +#[cfg(feature = "specialized-hashers")] +pub use crate::random_state::BuildAHasherFixed; +#[cfg(feature = "specialized-hashers")] +pub use crate::random_state::BuildAHasherStr; use core::hash::BuildHasher; use core::hash::Hash; diff --git a/src/random_state.rs b/src/random_state.rs index 54b754d..cbdb5ff 100644 --- a/src/random_state.rs +++ b/src/random_state.rs @@ -493,6 +493,125 @@ impl BuildHasherExt for RandomState { } } +/// A [`BuildHasher`] like [`RandomState`] but which builds an [`AHasherU64`] instead of an [`AHasher`]. +/// +/// The recommended way to acquire one is through [`Default`] or [`from_random_state`][BuildAHasherU64::from_random_state], +/// but if you want a fully-deterministically-seeded version then you can use the specialized [`BuildAHasherU64::with_seeds`]. +#[cfg(feature = "specialized-hashers")] +#[derive(Clone)] +pub struct BuildAHasherU64 { + k0: u64, + k1: u64, +} + +#[cfg(feature = "specialized-hashers")] +impl BuildAHasherU64 { + /// Convert a [`RandomState`] into a [`BuildAHasherU64`] + #[inline] + pub fn from_random_state(state: RandomState) -> Self { + Self { + k0: state.k0, + k1: state.k1, + } + } + + /// Allows for explicitly setting the seeds to used. + /// All `BuildAHasherU64`s created with the same set of keys key will produce identical hashers. + /// (In contrast to other ways of acquiring) + /// + /// Note: If DOS resistance is desired one of these should be a decent quality random number. + /// If 2 high quality random numbers are not cheaply available this method is robust against 0s being passed for + /// one or more of the parameters or the same value being passed for more than one parameter. + /// It is recommended to pass numbers in order from highest to lowest quality (if there is any difference). + #[inline] + pub const fn with_seeds(k0: u64, k1: u64) -> Self { + Self { + k0: k0 ^ PI2[0], + k1: k1 ^ PI2[1], + } + } + + /// Internal. Used by Default. + #[inline] + pub(crate) fn with_fixed_keys() -> Self { + let [k0, k1, _, _] = get_fixed_seeds()[0]; + Self { k0, k1 } + } + +} + +impl Default for BuildAHasherU64 { + #[inline] + fn default() -> Self { + Self::with_fixed_keys() + } +} + +#[cfg(feature = "specialized-hashers")] +impl BuildHasher for BuildAHasherU64 { + type Hasher = AHasherU64; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + AHasherU64 { + buffer: self.k0, + pad: self.k1, + } + } +} + +/// A [`BuildHasher`] like [`RandomState`] but which builds an [`AHasherFixed`] instead of an [`AHasher`]. +/// +/// Acquire one is through [`Default`] or [`from_random_state`][BuildAHasherFixed::from_random_state]. +#[cfg(feature = "specialized-hashers")] +#[derive(Default, Clone)] +pub struct BuildAHasherFixed(RandomState); + +#[cfg(feature = "specialized-hashers")] +impl BuildAHasherFixed { + /// Convert a [`RandomState`] into a [`BuildAHasherFixed`] + #[inline] + pub fn from_random_state(state: RandomState) -> Self { + Self(state) + } +} + +#[cfg(feature = "specialized-hashers")] +impl BuildHasher for BuildAHasherFixed { + type Hasher = AHasherFixed; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + AHasherFixed(self.0.build_hasher()) + } +} + +/// A [`BuildHasher`] like [`RandomState`] but which builds an [`AHasherStr`] instead of an [`AHasher`]. +/// +/// Acquire one is through [`Default`] or [`from_random_state`][BuildAHasherStr::from_random_state]. +#[cfg(feature = "specialized-hashers")] +#[derive(Default, Clone)] +pub struct BuildAHasherStr(RandomState); + +#[cfg(feature = "specialized-hashers")] +impl BuildAHasherStr { + /// Convert a [`RandomState`] into a [`BuildAHasherStr`] + #[inline] + pub fn from_random_state(state: RandomState) -> Self { + Self(state) + } +} + +#[cfg(feature = "specialized-hashers")] +impl BuildHasher for BuildAHasherStr { + type Hasher = AHasherStr; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + AHasherStr(self.0.build_hasher()) + } +} + #[cfg(test)] mod test { use super::*;