Skip to content

Commit 332d085

Browse files
committed
Save an instruction in EntityHasher
1 parent a01f21d commit 332d085

File tree

1 file changed

+37
-12
lines changed

1 file changed

+37
-12
lines changed

crates/bevy_utils/src/lib.rs

+37-12
Original file line numberDiff line numberDiff line change
@@ -266,29 +266,54 @@ impl BuildHasher for EntityHash {
266266
/// A very fast hash that is only designed to work on generational indices
267267
/// like `Entity`. It will panic if attempting to hash a type containing
268268
/// non-u64 fields.
269+
///
270+
/// This is heavily optimized for typical cases, where you have mostly live
271+
/// entities, and works particularly well for contiguous indices.
272+
///
273+
/// If you have an unusual case -- say all your indices are multiples of 256
274+
/// or most of the entities are dead generations -- then you might want also to
275+
/// try [`AHasher`] for a slower hash computation but fewer lookup conflicts.
269276
#[derive(Debug, Default)]
270277
pub struct EntityHasher {
271278
hash: u64,
272279
}
273280

274-
// This value comes from rustc-hash (also known as FxHasher) which in turn got
275-
// it from Firefox. It is something like `u64::MAX / N` for an N that gives a
276-
// value close to π and works well for distributing bits for hashing when using
277-
// with a wrapping multiplication.
278-
const FRAC_U64MAX_PI: u64 = 0x517cc1b727220a95;
279-
280281
impl Hasher for EntityHasher {
281282
fn write(&mut self, _bytes: &[u8]) {
282283
panic!("can only hash u64 using EntityHasher");
283284
}
284285

285286
#[inline]
286-
fn write_u64(&mut self, i: u64) {
287-
// Apparently hashbrown's hashmap uses the upper 7 bits for some SIMD
288-
// optimisation that uses those bits for binning. This hash function
289-
// was faster than i | (i << (64 - 7)) in the worst cases, and was
290-
// faster than PassHasher for all cases tested.
291-
self.hash = i | (i.wrapping_mul(FRAC_U64MAX_PI) << 32);
287+
fn write_u64(&mut self, bits: u64) {
288+
// SwissTable (and thus `hashbrown`) cares about two things from the hash:
289+
// - H1: low bits (masked by `2ⁿ-1`) to pick the slot in which to store the item
290+
// - H2: high 7 bits are used to SIMD optimize hash collision probing
291+
// For more see <https://abseil.io/about/design/swisstables#metadata-layout>
292+
293+
// This hash function assumes that the entity ids are still well-distributed,
294+
// so for H1 leaves the entity id alone in the low bits so that id locality
295+
// will also give memory locality for things spawned together.
296+
// For H2, take advantage of the fact that while multiplication doesn't
297+
// spread entropy to the low bits, it's incredibly good at spreading it
298+
// upward, which is exactly where we need it the most.
299+
300+
// While this does include the generation in the output, it doesn't do so
301+
// *usefully*. H1 won't care until you have over 3 billion entities in
302+
// the table, and H2 won't care until something hits generation 33 million.
303+
// Thus the comment suggesting that this is best for live entities,
304+
// where there won't be generation conflicts where it would matter.
305+
306+
// The high 32 bits of this are ⅟φ for Fibonacci hashing. That works
307+
// particularly well for hashing for the same reason as described in
308+
// <https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/>
309+
// It loses no information because it has a modular inverse.
310+
// (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.)
311+
//
312+
// The low 32 bits make that part of the just product a pass-through.
313+
const UPPER_PHI: u64 = 0x9e37_79b9_0000_0001;
314+
315+
// This is `(MAGIC * index + generation) << 32 + index`, in a single instruction.
316+
self.hash = bits.wrapping_mul(UPPER_PHI);
292317
}
293318

294319
#[inline]

0 commit comments

Comments
 (0)