Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose specialized versions of AHasher through specialized-hashers feature #156

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 20 additions & 13 deletions src/aes_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
/// println!("Hash is {:x}!", hasher.finish());
/// ```
#[inline]
pub(crate) fn new_with_keys(key1: u128, key2: u128) -> Self {

Check warning on line 51 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / Linux x86_64 - nightly

associated function `new_with_keys` is never used

Check warning on line 51 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / Linux x86_64 - nightly

associated function `new_with_keys` is never used

Check warning on line 51 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / nightly

associated function `new_with_keys` is never used

Check warning on line 51 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / nightly

associated function `new_with_keys` is never used

Check warning on line 51 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / nightly

associated function `new_with_keys` is never used

Check warning on line 51 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / nightly

associated function `new_with_keys` is never used
let pi: [u128; 2] = PI.convert();
let key1 = key1 ^ pi[0];
let key2 = key2 ^ pi[1];
Expand Down Expand Up @@ -213,14 +213,16 @@
}
}

#[cfg(feature = "specialize")]
pub(crate) struct AHasherU64 {
/// A specialized hasher for only primitives <= 64 bits.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are public, they should have a bit more docs. It is probably worth enumerating the types that this works with. (Notably usize is not one, as it can technically be 128 bits on some exotic systems)

///
/// 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 {
Expand Down Expand Up @@ -264,11 +266,13 @@
}
}

#[cfg(feature = "specialize")]
pub(crate) struct AHasherFixed(pub AHasher);

/// A specialized hasher for fixed size primitives larger than 64 bits.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't have to be strictly primitives, just fixed in size. So for example a (u128, u128) would be ok with this. It just hasn't been used that way now as I don't have a way to detect such arbitrary structures.

#[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 {
Expand Down Expand Up @@ -311,12 +315,15 @@
}
}

#[cfg(feature = "specialize")]
pub(crate) struct AHasherStr(pub AHasher);
/// A specialized hasher for strings.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for anything that can deref / cast to a [u8] so, String, str, slices of primitives etc.

///
/// 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 {
Expand Down Expand Up @@ -361,7 +368,7 @@

#[cfg(test)]
mod tests {
use super::*;

Check warning on line 371 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / nightly

unused import: `super::*`

Check warning on line 371 in src/aes_hash.rs

View workflow job for this annotation

GitHub Actions / nightly

unused import: `super::*`
use crate::convert::Convert;
use crate::operations::aesenc;
use crate::RandomState;
Expand Down
35 changes: 21 additions & 14 deletions src/fallback_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
119 changes: 119 additions & 0 deletions src/random_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
#[cfg(not(feature = "atomic-polyfill"))]
use core::sync::atomic;

use alloc::boxed::Box;

Check warning on line 30 in src/random_state.rs

View workflow job for this annotation

GitHub Actions / thumbv6m

unused import: `alloc::boxed::Box`
use atomic::{AtomicUsize, Ordering};
use core::any::{Any, TypeId};

Check warning on line 32 in src/random_state.rs

View workflow job for this annotation

GitHub Actions / thumbv6m

unused import: `TypeId`

Check warning on line 32 in src/random_state.rs

View workflow job for this annotation

GitHub Actions / thumbv6m

unused import: `Any`
use core::fmt;
use core::hash::BuildHasher;
use core::hash::Hasher;
Expand Down Expand Up @@ -493,6 +493,125 @@
}
}

/// 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 {

Check failure on line 543 in src/random_state.rs

View workflow job for this annotation

GitHub Actions / build

cannot find type `BuildAHasherU64` in this scope

Check failure on line 543 in src/random_state.rs

View workflow job for this annotation

GitHub Actions / thumbv6m

cannot find type `BuildAHasherU64` in this scope

Check failure on line 543 in src/random_state.rs

View workflow job for this annotation

GitHub Actions / wasm

cannot find type `BuildAHasherU64` in this scope
#[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::*;
Expand Down