@@ -266,29 +266,47 @@ impl BuildHasher for EntityHash {
266
266
/// A very fast hash that is only designed to work on generational indices
267
267
/// like `Entity`. It will panic if attempting to hash a type containing
268
268
/// non-u64 fields.
269
+ ///
270
+ /// This is heavily optimized for typical cases, where there are lots of runs
271
+ /// of contiguous indices and almost no generation conflicts.
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.
269
276
#[ derive( Debug , Default ) ]
270
277
pub struct EntityHasher {
271
278
hash : u64 ,
272
279
}
273
280
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
-
280
281
impl Hasher for EntityHasher {
281
282
fn write ( & mut self , _bytes : & [ u8 ] ) {
282
283
panic ! ( "can only hash u64 using EntityHasher" ) ;
283
284
}
284
285
285
286
#[ inline]
286
287
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 ) ;
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
+ // The high 32 bits of this are ⅟φ for Fibonacci hashing. That works
301
+ // particularly well for hashing for the same reason as described in
302
+ // <https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/>
303
+ // It loses no information because it has a modular inverse.
304
+ // (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.)
305
+ // The low 32 bits are just 1, to leave the entity id there unchanged.
306
+ const UPPER_PHI : u64 = 0x9e37_79b9_0000_0001 ;
307
+ // This bit-masking is free, as the optimizer can just not load the generation.
308
+ let id = i & 0xFFFF_FFFF ;
309
+ self . hash = id. wrapping_mul ( UPPER_PHI ) ;
292
310
}
293
311
294
312
#[ inline]
0 commit comments