Skip to content

Commit

Permalink
Tweak the structure slightly for some extra clarity
Browse files Browse the repository at this point in the history
(No functional difference; just rearranged.)
  • Loading branch information
scottmcm committed Nov 18, 2023
1 parent 5c48fd7 commit 2eb0f56
Showing 1 changed file with 20 additions and 9 deletions.
29 changes: 20 additions & 9 deletions crates/bevy_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl BuildHasher for EntityHash {
/// try [`AHasher`] for a slower hash computation but fewer lookup conflicts.
#[derive(Debug, Default)]
pub struct EntityHasher {
hash: u64,
index: u32,
}

impl Hasher for EntityHasher {
Expand All @@ -285,6 +285,23 @@ impl Hasher for EntityHasher {

#[inline]
fn write_u64(&mut self, i: u64) {
// We ignore the generation entirely. It's always functionally correct
// to omit things when hashing, so long as it's consistent, just a perf
// trade-off. This hasher is designed for "normal" cases, where nearly
// everything in the table is a live entity, meaning there are few
// generation conflicts. And thus it's overall faster to just ignore
// the generation during hashing, leaving it to the `Entity::eq` to
// confirm the generation matches -- just like `Entity::eq` checks that
// the index is actually the right one, since there's always the chance
// of a conflict in the index despite a good hash function.
//
// This truncation actually ends up with negative cost after optimization,
// as the optimizer will just skip loading the generation at all.
self.index = i as u32;
}

#[inline]
fn finish(&self) -> u64 {
// SwissTable (and thus `hashbrown`) cares about two things from the hash:
// - H1: low bits (masked by `2ⁿ-1`) to pick the slot in which to store the item
// - H2: high 7 bits are used to SIMD optimize hash collision probing
Expand All @@ -302,16 +319,10 @@ impl Hasher for EntityHasher {
// <https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/>
// It loses no information because it has a modular inverse.
// (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.)
//
// The low 32 bits are just 1, to leave the entity id there unchanged.
const UPPER_PHI: u64 = 0x9e37_79b9_0000_0001;
// This bit-masking is free, as the optimizer can just not load the generation.
let id = i & 0xFFFF_FFFF;
self.hash = id.wrapping_mul(UPPER_PHI);
}

#[inline]
fn finish(&self) -> u64 {
self.hash
u64::from(self.index).wrapping_mul(UPPER_PHI)
}
}

Expand Down

0 comments on commit 2eb0f56

Please sign in to comment.