Skip to content

Commit

Permalink
Use fxhash in TypeIdMap. (bevyengine#1119)
Browse files Browse the repository at this point in the history
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](bevyengine#1097)
[Engine wide benchmarks](bevyengine#1119 (comment))
  • Loading branch information
AngelicosPhosphoros authored and rparrett committed Jan 27, 2021
1 parent 3dc1310 commit 07e627f
Show file tree
Hide file tree
Showing 2 changed files with 6 additions and 43 deletions.
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
48 changes: 5 additions & 43 deletions crates/bevy_ecs/src/core/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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>;

0 comments on commit 07e627f

Please sign in to comment.