Use fxhash in TypeIdMap. (#1119)

Relying on TypeId being some hash internally isn't future-proof because there is no guarantee about internal layout or structure of TypeId. I benchmarked TypeId noop hasher vs fxhash and found that there is very little difference.
Also fxhash is likely to be better supported because it is widely used in rustc itself.
[Benchmarks of hashers](https://github.com/bevyengine/bevy/issues/1097)
[Engine wide benchmarks](https://github.com/bevyengine/bevy/pull/1119#issuecomment-751361215)
This commit is contained in:
AngelicosPhosphoros 2021-01-08 04:42:09 +03:00 committed by GitHub
parent 0e0906d22e
commit 9bce8712b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 6 additions and 43 deletions

View file

@ -20,6 +20,7 @@ trace = []
bevy_tasks = { path = "../bevy_tasks", version = "0.4.0" }
bevy_utils = { path = "../bevy_utils", version = "0.4.0" }
bevy_ecs_macros = { path = "macros", version = "0.4.0" }
fxhash = "0.2"
rand = "0.7.3"
serde = "1.0"
thiserror = "1.0"

View file

@ -15,14 +15,12 @@
// modified by Bevy contributors
use crate::{AtomicBorrow, Component, Entity};
use bevy_utils::AHasher;
use bitflags::bitflags;
use std::{
alloc::{alloc, dealloc, Layout},
any::{type_name, TypeId},
cell::UnsafeCell,
collections::HashMap,
hash::{BuildHasherDefault, Hasher},
mem,
ptr::{self, NonNull},
};
@ -557,44 +555,8 @@ fn align(x: usize, alignment: usize) -> usize {
/// A hasher optimized for hashing a single TypeId.
///
/// TypeId is already thoroughly hashed, so there's no reason to hash it again.
/// Just leave the bits unchanged.
#[derive(Default)]
pub(crate) struct TypeIdHasher {
hash: u64,
}
impl Hasher for TypeIdHasher {
fn write_u64(&mut self, n: u64) {
// Only a single value can be hashed, so the old hash should be zero.
debug_assert_eq!(self.hash, 0);
self.hash = n;
}
// Tolerate TypeId being either u64 or u128.
fn write_u128(&mut self, n: u128) {
debug_assert_eq!(self.hash, 0);
self.hash = n as u64;
}
fn write(&mut self, bytes: &[u8]) {
debug_assert_eq!(self.hash, 0);
// This will only be called if TypeId is neither u64 nor u128, which is not anticipated.
// In that case we'll just fall back to using a different hash implementation.
let mut hasher = AHasher::default();
hasher.write(bytes);
self.hash = hasher.finish();
}
fn finish(&self) -> u64 {
self.hash
}
}
/// A HashMap with TypeId keys
///
/// Because TypeId is already a fully-hashed u64 (including data in the high seven bits,
/// which hashbrown needs), there is no need to hash it again. Instead, this uses the much
/// faster no-op hash.
pub(crate) type TypeIdMap<V> = HashMap<TypeId, V, BuildHasherDefault<TypeIdHasher>>;
/// We don't use RandomState from std or Random state from Ahash
/// because fxhash is [proved to be faster](https://github.com/bevyengine/bevy/pull/1119#issuecomment-751361215)
/// and we don't need Hash Dos attack protection here
/// since TypeIds generated during compilation and there is no reason to user attack himself.
pub(crate) type TypeIdMap<V> = HashMap<TypeId, V, fxhash::FxBuildHasher>;