From 866f758ca16b18121f24149cb7c1ae0758fc4a50 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 15 Feb 2022 18:22:11 -0800 Subject: [PATCH 1/9] use hashbrown for bevy hashmaps / hashsets --- crates/bevy_asset/src/asset_server.rs | 4 +- crates/bevy_ecs/src/entity/map_entities.rs | 3 +- crates/bevy_input/src/input.rs | 4 +- crates/bevy_reflect/Cargo.toml | 1 + crates/bevy_reflect/src/impls/std.rs | 2 +- crates/bevy_reflect/src/map.rs | 4 +- crates/bevy_reflect/src/struct_trait.rs | 4 +- .../src/render_resource/pipeline_cache.rs | 4 +- .../bevy_render/src/texture/texture_cache.rs | 6 +- crates/bevy_utils/Cargo.toml | 1 + crates/bevy_utils/src/lib.rs | 173 +++++++++--------- 11 files changed, 104 insertions(+), 102 deletions(-) diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index f77d677c89271..97296abc62393 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -8,10 +8,10 @@ use anyhow::Result; use bevy_ecs::system::{Res, ResMut}; use bevy_log::warn; use bevy_tasks::TaskPool; -use bevy_utils::{HashMap, Uuid}; +use bevy_utils::{Entry, HashMap, Uuid}; use crossbeam_channel::TryRecvError; use parking_lot::{Mutex, RwLock}; -use std::{collections::hash_map::Entry, path::Path, sync::Arc}; +use std::{path::Path, sync::Arc}; use thiserror::Error; /// Errors that occur while loading assets with an `AssetServer` diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index b6cf162177859..00080bd0edd2f 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -1,6 +1,5 @@ use crate::entity::Entity; -use bevy_utils::HashMap; -use std::collections::hash_map::Entry; +use bevy_utils::{Entry, HashMap}; use thiserror::Error; #[derive(Error, Debug)] diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index b98e2b25e85ca..328e162561baa 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -29,13 +29,13 @@ use bevy_ecs::schedule::State; /// * Call the [`Input::release`] method for each release event. /// * Call the [`Input::clear`] method at each frame start, before processing events. #[derive(Debug, Clone)] -pub struct Input { +pub struct Input { pressed: HashSet, just_pressed: HashSet, just_released: HashSet, } -impl Default for Input { +impl Default for Input { fn default() -> Self { Self { pressed: Default::default(), diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index f437a31a88dd8..9d5c431e6ddd6 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -25,6 +25,7 @@ thiserror = "1.0" serde = "1" smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true } glam = { version = "0.20.0", features = ["serde"], optional = true } +hashbrown = { version = "0.11", features = ["serde"], optional = true } [dev-dependencies] ron = "0.7.0" diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 1f600abcefc18..0b1d4097e1e11 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -6,7 +6,7 @@ use crate::{ }; use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; -use bevy_utils::{AHashExt, Duration, HashMap, HashSet}; +use bevy_utils::{Duration, HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::{ any::Any, diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index a123df1737852..4fdba328c6714 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -1,6 +1,6 @@ -use std::{any::Any, collections::hash_map::Entry}; +use std::any::Any; -use bevy_utils::HashMap; +use bevy_utils::{Entry, HashMap}; use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index f85c46cbcfa4c..f8a413e6f6030 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,6 +1,6 @@ use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; -use bevy_utils::HashMap; -use std::{any::Any, borrow::Cow, collections::hash_map::Entry}; +use bevy_utils::{Entry, HashMap}; +use std::{any::Any, borrow::Cow}; /// A reflected Rust regular struct type. /// diff --git a/crates/bevy_render/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs index d4ec249133ee8..77ed782eb61cb 100644 --- a/crates/bevy_render/src/render_resource/pipeline_cache.rs +++ b/crates/bevy_render/src/render_resource/pipeline_cache.rs @@ -10,8 +10,8 @@ use crate::{ use bevy_app::EventReader; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::system::{Res, ResMut}; -use bevy_utils::{tracing::error, HashMap, HashSet}; -use std::{collections::hash_map::Entry, hash::Hash, ops::Deref, sync::Arc}; +use bevy_utils::{tracing::error, Entry, HashMap, HashSet}; +use std::{hash::Hash, ops::Deref, sync::Arc}; use thiserror::Error; use wgpu::{PipelineLayoutDescriptor, ShaderModule, VertexBufferLayout}; diff --git a/crates/bevy_render/src/texture/texture_cache.rs b/crates/bevy_render/src/texture/texture_cache.rs index cca353a1d5e62..43cac472c154f 100644 --- a/crates/bevy_render/src/texture/texture_cache.rs +++ b/crates/bevy_render/src/texture/texture_cache.rs @@ -3,7 +3,7 @@ use crate::{ renderer::RenderDevice, }; use bevy_ecs::prelude::ResMut; -use bevy_utils::HashMap; +use bevy_utils::{Entry, HashMap}; use wgpu::{TextureDescriptor, TextureViewDescriptor}; /// The internal representation of a [`CachedTexture`] used to track whether it was recently used @@ -39,7 +39,7 @@ impl TextureCache { descriptor: TextureDescriptor<'static>, ) -> CachedTexture { match self.textures.entry(descriptor) { - std::collections::hash_map::Entry::Occupied(mut entry) => { + Entry::Occupied(mut entry) => { for texture in entry.get_mut().iter_mut() { if !texture.taken { texture.frames_since_last_use = 0; @@ -64,7 +64,7 @@ impl TextureCache { default_view, } } - std::collections::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { let texture = render_device.create_texture(entry.key()); let default_view = texture.create_view(&TextureViewDescriptor::default()); entry.insert(vec![CachedTextureMeta { diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 8d65543b2cfde..c6b93f336d695 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -14,6 +14,7 @@ ahash = "0.7.0" tracing = {version = "0.1", features = ["release_max_level_info"]} instant = { version = "0.1", features = ["wasm-bindgen"] } uuid = { version = "0.8", features = ["v4", "serde"] } +hashbrown = { version = "0.11", features = ["serde"] } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = {version = "0.2.0", features = ["js"]} diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index eeb0ba668311d..9a177ef8c7ee8 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -3,6 +3,7 @@ pub mod label; pub use ahash::AHasher; pub use enum_variant_meta::*; +pub type Entry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, RandomState>; pub use instant::{Duration, Instant}; pub use tracing; pub use uuid::Uuid; @@ -32,7 +33,7 @@ impl std::hash::BuildHasher for FixedState { } } -/// A [`HashMap`][std::collections::HashMap] implementing [`aHash`], a high +/// A [`HashMap`][hashbrown::HashMap] implementing [`aHash`], a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// /// `aHash` is designed for performance and is NOT cryptographically secure. @@ -50,7 +51,7 @@ impl std::hash::BuildHasher for FixedState { /// # } /// ``` /// -/// The standard library's [`HashMap::new`][std::collections::HashMap::new] is +/// The standard library's [`HashMap::new`][hashbrown::HashMap::new] is /// implemented only for `HashMap`s which use the /// [`DefaultHasher`][std::collections::hash_map::DefaultHasher], so it's not /// available for Bevy's `HashMap`. @@ -69,30 +70,30 @@ impl std::hash::BuildHasher for FixedState { /// ``` /// /// [`aHash`]: https://github.com/tkaitchuck/aHash -pub type HashMap = std::collections::HashMap; - -pub trait AHashExt { - fn with_capacity(capacity: usize) -> Self; -} - -impl AHashExt for HashMap { - /// Creates an empty `HashMap` with the specified capacity with aHash. - /// - /// The hash map will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash map will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{HashMap, AHashExt}; - /// let mut map: HashMap<&str, i32> = HashMap::with_capacity(10); - /// assert!(map.capacity() >= 10); - /// ``` - #[inline] - fn with_capacity(capacity: usize) -> Self { - HashMap::with_capacity_and_hasher(capacity, RandomState::default()) - } -} +pub type HashMap = hashbrown::HashMap; + +// pub trait AHashExt { +// fn with_capacity(capacity: usize) -> Self; +// } + +// impl AHashExt for HashMap { +// /// Creates an empty `HashMap` with the specified capacity with aHash. +// /// +// /// The hash map will be able to hold at least `capacity` elements without +// /// reallocating. If `capacity` is 0, the hash map will not allocate. +// /// +// /// # Examples +// /// +// /// ``` +// /// use bevy_utils::{HashMap, AHashExt}; +// /// let mut map: HashMap<&str, i32> = HashMap::with_capacity(10); +// /// assert!(map.capacity() >= 10); +// /// ``` +// #[inline] +// fn with_capacity(capacity: usize) -> Self { +// HashMap::with_capacity_and_hasher(capacity, RandomState::default()) +// } +// } /// A stable std hash map implementing `aHash`, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. @@ -101,26 +102,26 @@ impl AHashExt for HashMap { /// of insertions and deletions and not a random source. /// /// `aHash` is designed for performance and is NOT cryptographically secure. -pub type StableHashMap = std::collections::HashMap; - -impl AHashExt for StableHashMap { - /// Creates an empty `StableHashMap` with the specified capacity with `aHash`. - /// - /// The hash map will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash map will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{StableHashMap, AHashExt}; - /// let mut map: StableHashMap<&str, i32> = StableHashMap::with_capacity(10); - /// assert!(map.capacity() >= 10); - /// ``` - #[inline] - fn with_capacity(capacity: usize) -> Self { - StableHashMap::with_capacity_and_hasher(capacity, FixedState::default()) - } -} +pub type StableHashMap = hashbrown::HashMap; + +// impl AHashExt for StableHashMap { +// /// Creates an empty `StableHashMap` with the specified capacity with `aHash`. +// /// +// /// The hash map will be able to hold at least `capacity` elements without +// /// reallocating. If `capacity` is 0, the hash map will not allocate. +// /// +// /// # Examples +// /// +// /// ``` +// /// use bevy_utils::{StableHashMap, AHashExt}; +// /// let mut map: StableHashMap<&str, i32> = StableHashMap::with_capacity(10); +// /// assert!(map.capacity() >= 10); +// /// ``` +// #[inline] +// fn with_capacity(capacity: usize) -> Self { +// StableHashMap::with_capacity_and_hasher(capacity, FixedState::default()) +// } +// } /// A [`HashSet`][std::collections::HashSet] implementing [`aHash`], a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. @@ -159,26 +160,26 @@ impl AHashExt for StableHashMap { /// ``` /// /// [`aHash`]: https://github.com/tkaitchuck/aHash -pub type HashSet = std::collections::HashSet; - -impl AHashExt for HashSet { - /// Creates an empty `HashSet` with the specified capacity with aHash. - /// - /// The hash set will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash set will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{HashSet, AHashExt}; - /// let set: HashSet = HashSet::with_capacity(10); - /// assert!(set.capacity() >= 10); - /// ``` - #[inline] - fn with_capacity(capacity: usize) -> Self { - HashSet::with_capacity_and_hasher(capacity, RandomState::default()) - } -} +pub type HashSet = hashbrown::HashSet; + +// impl AHashExt for HashSet { +// /// Creates an empty `HashSet` with the specified capacity with aHash. +// /// +// /// The hash set will be able to hold at least `capacity` elements without +// /// reallocating. If `capacity` is 0, the hash set will not allocate. +// /// +// /// # Examples +// /// +// /// ``` +// /// use bevy_utils::{HashSet, AHashExt}; +// /// let set: HashSet = HashSet::with_capacity(10); +// /// assert!(set.capacity() >= 10); +// /// ``` +// #[inline] +// fn with_capacity(capacity: usize) -> Self { +// HashSet::with_capacity_and_hasher(capacity, RandomState::default()) +// } +// } /// A stable std hash set implementing `aHash`, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. @@ -187,23 +188,23 @@ impl AHashExt for HashSet { /// of insertions and deletions and not a random source. /// /// `aHash` is designed for performance and is NOT cryptographically secure. -pub type StableHashSet = std::collections::HashSet; - -impl AHashExt for StableHashSet { - /// Creates an empty `StableHashSet` with the specified capacity with `aHash`. - /// - /// The hash set will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash set will not allocate. - /// - /// # Examples - /// - /// ``` - /// use bevy_utils::{StableHashSet, AHashExt}; - /// let set: StableHashSet = StableHashSet::with_capacity(10); - /// assert!(set.capacity() >= 10); - /// ``` - #[inline] - fn with_capacity(capacity: usize) -> Self { - StableHashSet::with_capacity_and_hasher(capacity, FixedState::default()) - } -} +pub type StableHashSet = hashbrown::HashSet; + +// impl AHashExt for StableHashSet { +// /// Creates an empty `StableHashSet` with the specified capacity with `aHash`. +// /// +// /// The hash set will be able to hold at least `capacity` elements without +// /// reallocating. If `capacity` is 0, the hash set will not allocate. +// /// +// /// # Examples +// /// +// /// ``` +// /// use bevy_utils::{StableHashSet, AHashExt}; +// /// let set: StableHashSet = StableHashSet::with_capacity(10); +// /// assert!(set.capacity() >= 10); +// /// ``` +// #[inline] +// fn with_capacity(capacity: usize) -> Self { +// StableHashSet::with_capacity_and_hasher(capacity, FixedState::default()) +// } +// } From 0f5d298d5035818156ee341bfbb40eba65422220 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 15 Feb 2022 20:45:27 -0800 Subject: [PATCH 2/9] proper prehashing --- crates/bevy_utils/src/lib.rs | 146 ++++++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 9a177ef8c7ee8..bb3f558d6cf78 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -4,12 +4,21 @@ pub mod label; pub use ahash::AHasher; pub use enum_variant_meta::*; pub type Entry<'a, K, V> = hashbrown::hash_map::Entry<'a, K, V, RandomState>; +pub use hashbrown; +use hashbrown::hash_map::RawEntryMut; pub use instant::{Duration, Instant}; pub use tracing; pub use uuid::Uuid; use ahash::RandomState; -use std::{future::Future, pin::Pin}; +use std::{ + fmt::Debug, + future::Future, + hash::{BuildHasher, Hash, Hasher}, + marker::PhantomData, + ops::Deref, + pin::Pin, +}; #[cfg(not(target_arch = "wasm32"))] pub type BoxedFuture<'a, T> = Pin + Send + 'a>>; @@ -208,3 +217,138 @@ pub type StableHashSet = hashbrown::HashSet; // StableHashSet::with_capacity_and_hasher(capacity, FixedState::default()) // } // } + +pub struct Hashed { + hash: u64, + value: V, + marker: PhantomData, +} + +impl Hashed { + pub fn new(value: V) -> Self { + let builder = H::default(); + let mut hasher = builder.build_hasher(); + value.hash(&mut hasher); + Self { + hash: hasher.finish(), + value, + marker: PhantomData, + } + } + + #[inline] + pub fn hash(&self) -> u64 { + self.hash + } +} + +impl Hash for Hashed { + #[inline] + fn hash(&self, state: &mut R) { + state.write_u64(self.hash); + } +} + +impl Deref for Hashed { + type Target = V; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl Hashed { + #[inline] + pub fn fast_eq(&self, other: &Hashed) -> bool { + // Makes the common case of two values not been equal very fast + self.hash == other.hash && self.value.eq(&other.value) + } +} + +impl PartialEq for Hashed { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.value.eq(&other.value) + } +} + +impl Debug for Hashed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Hashed") + .field("hash", &self.hash) + .field("value", &self.value) + .finish() + } +} + +impl Clone for Hashed { + #[inline] + fn clone(&self) -> Self { + Self { + hash: self.hash.clone(), + value: self.value.clone(), + marker: PhantomData, + } + } +} + +impl Eq for Hashed {} + +#[derive(Default)] +pub struct PassHash; + +impl BuildHasher for PassHash { + type Hasher = PassHasher; + + fn build_hasher(&self) -> Self::Hasher { + PassHasher::default() + } +} + +#[derive(Debug)] +pub struct PassHasher { + hash: u64, +} + +impl Default for PassHasher { + fn default() -> Self { + Self { hash: 0 } + } +} + +impl Hasher for PassHasher { + fn write(&mut self, _bytes: &[u8]) { + panic!("cannot hash byte arrays using PassHasher"); + } + + fn write_u64(&mut self, i: u64) { + self.hash = i; + } + + fn finish(&self) -> u64 { + self.hash + } +} + +pub type PreHashMap = hashbrown::HashMap, V, PassHash>; + +pub trait PreHashMapExt { + fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V; +} + +impl PreHashMapExt for PreHashMap { + #[inline] + fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V { + let entry = self + .raw_entry_mut() + .from_key_hashed_nocheck(key.hash(), key); + match entry { + RawEntryMut::Occupied(entry) => entry.into_mut(), + RawEntryMut::Vacant(entry) => { + let (_, value) = entry.insert_hashed_nocheck(key.hash(), key.clone(), func()); + value + } + } + } +} From 9d4c30e59e3a7e986015fb3a8e9b5245902e0d33 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 16 Feb 2022 14:31:46 -0800 Subject: [PATCH 3/9] remove commented out code --- crates/bevy_utils/src/lib.rs | 82 +----------------------------------- 1 file changed, 1 insertion(+), 81 deletions(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index bb3f558d6cf78..2f83b7cedadf6 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -81,29 +81,6 @@ impl std::hash::BuildHasher for FixedState { /// [`aHash`]: https://github.com/tkaitchuck/aHash pub type HashMap = hashbrown::HashMap; -// pub trait AHashExt { -// fn with_capacity(capacity: usize) -> Self; -// } - -// impl AHashExt for HashMap { -// /// Creates an empty `HashMap` with the specified capacity with aHash. -// /// -// /// The hash map will be able to hold at least `capacity` elements without -// /// reallocating. If `capacity` is 0, the hash map will not allocate. -// /// -// /// # Examples -// /// -// /// ``` -// /// use bevy_utils::{HashMap, AHashExt}; -// /// let mut map: HashMap<&str, i32> = HashMap::with_capacity(10); -// /// assert!(map.capacity() >= 10); -// /// ``` -// #[inline] -// fn with_capacity(capacity: usize) -> Self { -// HashMap::with_capacity_and_hasher(capacity, RandomState::default()) -// } -// } - /// A stable std hash map implementing `aHash`, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// @@ -113,25 +90,6 @@ pub type HashMap = hashbrown::HashMap; /// `aHash` is designed for performance and is NOT cryptographically secure. pub type StableHashMap = hashbrown::HashMap; -// impl AHashExt for StableHashMap { -// /// Creates an empty `StableHashMap` with the specified capacity with `aHash`. -// /// -// /// The hash map will be able to hold at least `capacity` elements without -// /// reallocating. If `capacity` is 0, the hash map will not allocate. -// /// -// /// # Examples -// /// -// /// ``` -// /// use bevy_utils::{StableHashMap, AHashExt}; -// /// let mut map: StableHashMap<&str, i32> = StableHashMap::with_capacity(10); -// /// assert!(map.capacity() >= 10); -// /// ``` -// #[inline] -// fn with_capacity(capacity: usize) -> Self { -// StableHashMap::with_capacity_and_hasher(capacity, FixedState::default()) -// } -// } - /// A [`HashSet`][std::collections::HashSet] implementing [`aHash`], a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// @@ -171,25 +129,6 @@ pub type StableHashMap = hashbrown::HashMap; /// [`aHash`]: https://github.com/tkaitchuck/aHash pub type HashSet = hashbrown::HashSet; -// impl AHashExt for HashSet { -// /// Creates an empty `HashSet` with the specified capacity with aHash. -// /// -// /// The hash set will be able to hold at least `capacity` elements without -// /// reallocating. If `capacity` is 0, the hash set will not allocate. -// /// -// /// # Examples -// /// -// /// ``` -// /// use bevy_utils::{HashSet, AHashExt}; -// /// let set: HashSet = HashSet::with_capacity(10); -// /// assert!(set.capacity() >= 10); -// /// ``` -// #[inline] -// fn with_capacity(capacity: usize) -> Self { -// HashSet::with_capacity_and_hasher(capacity, RandomState::default()) -// } -// } - /// A stable std hash set implementing `aHash`, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// @@ -199,25 +138,6 @@ pub type HashSet = hashbrown::HashSet; /// `aHash` is designed for performance and is NOT cryptographically secure. pub type StableHashSet = hashbrown::HashSet; -// impl AHashExt for StableHashSet { -// /// Creates an empty `StableHashSet` with the specified capacity with `aHash`. -// /// -// /// The hash set will be able to hold at least `capacity` elements without -// /// reallocating. If `capacity` is 0, the hash set will not allocate. -// /// -// /// # Examples -// /// -// /// ``` -// /// use bevy_utils::{StableHashSet, AHashExt}; -// /// let set: StableHashSet = StableHashSet::with_capacity(10); -// /// assert!(set.capacity() >= 10); -// /// ``` -// #[inline] -// fn with_capacity(capacity: usize) -> Self { -// StableHashSet::with_capacity_and_hasher(capacity, FixedState::default()) -// } -// } - pub struct Hashed { hash: u64, value: V, @@ -261,7 +181,7 @@ impl Deref for Hashed { impl Hashed { #[inline] pub fn fast_eq(&self, other: &Hashed) -> bool { - // Makes the common case of two values not been equal very fast + // Makes the common case of two values not being equal very fast self.hash == other.hash && self.value.eq(&other.value) } } From 18a40aebf5ddc634d91d17e0817c632d16b7754b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 16 Feb 2022 14:32:23 -0800 Subject: [PATCH 4/9] bevy_ecs: replace incorrect pre-hashing strategy --- crates/bevy_ecs/src/storage/table.rs | 30 +++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index 792501131542c..c8cdbfd91fbb6 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -3,10 +3,9 @@ use crate::{ entity::Entity, storage::{BlobVec, SparseSet}, }; -use bevy_utils::{AHasher, HashMap}; +use bevy_utils::HashMap; use std::{ cell::UnsafeCell, - hash::{Hash, Hasher}, ops::{Index, IndexMut}, ptr::NonNull, }; @@ -415,7 +414,7 @@ impl Table { /// Can be accessed via [`Storages`](crate::storage::Storages) pub struct Tables { tables: Vec, - table_ids: HashMap, + table_ids: HashMap, TableId>, } impl Default for Tables { @@ -472,18 +471,21 @@ impl Tables { component_ids: &[ComponentId], components: &Components, ) -> TableId { - let mut hasher = AHasher::default(); - component_ids.hash(&mut hasher); - let hash = hasher.finish(); let tables = &mut self.tables; - *self.table_ids.entry(hash).or_insert_with(move || { - let mut table = Table::with_capacity(0, component_ids.len()); - for component_id in component_ids.iter() { - table.add_column(components.get_info_unchecked(*component_id)); - } - tables.push(table); - TableId(tables.len() - 1) - }) + let (_key, value) = self + .table_ids + .raw_entry_mut() + .from_key(component_ids) + .or_insert_with(|| { + let mut table = Table::with_capacity(0, component_ids.len()); + for component_id in component_ids.iter() { + table.add_column(components.get_info_unchecked(*component_id)); + } + tables.push(table); + (component_ids.to_vec(), TableId(tables.len() - 1)) + }); + + *value } pub fn iter(&self) -> std::slice::Iter<'_, Table> { From cd7131f445d1f54482895f1eedfbbdafedd6119d Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 16 Feb 2022 14:54:40 -0800 Subject: [PATCH 5/9] Add docs --- crates/bevy_utils/src/lib.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 2f83b7cedadf6..deb9ef27359ee 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -138,6 +138,11 @@ pub type HashSet = hashbrown::HashSet; /// `aHash` is designed for performance and is NOT cryptographically secure. pub type StableHashSet = hashbrown::HashSet; +/// A pre-hashed value of a specific type. Pre-hashing enables memoization of hashes that are expensive to compute. +/// It also enables accelerated comparisons using [`Hashed::fast_eq`]. +/// See [`PassHash`] and [`PassHasher`] for a "pass through" [`BuildHasher`] and [`Hasher`] implementation +/// designed to work with [`Hashed`] +/// See [`PreHashMap`] for a hashmap pre-configured to use [`Hashed`] keys. pub struct Hashed { hash: u64, value: V, @@ -145,6 +150,7 @@ pub struct Hashed { } impl Hashed { + /// Pre-hashes the given value using the [`BuildHasher`] configured in the [`Hashed`] type. pub fn new(value: V) -> Self { let builder = H::default(); let mut hasher = builder.build_hasher(); @@ -156,6 +162,7 @@ impl Hashed { } } + /// The pre-computed hash. #[inline] pub fn hash(&self) -> u64 { self.hash @@ -179,9 +186,11 @@ impl Deref for Hashed { } impl Hashed { + /// A faster version of [`PartialEq`] that first checks that `other`'s pre-computed hash + /// matches this value's pre-computed hash. For complex types, this can be much faster than + /// [`PartialEq`]. #[inline] pub fn fast_eq(&self, other: &Hashed) -> bool { - // Makes the common case of two values not being equal very fast self.hash == other.hash && self.value.eq(&other.value) } } @@ -215,6 +224,7 @@ impl Clone for Hashed { impl Eq for Hashed {} +/// A [`BuildHasher`] that results in a [`PassHasher`]. #[derive(Default)] pub struct PassHash; @@ -232,6 +242,7 @@ pub struct PassHasher { } impl Default for PassHasher { + #[inline] fn default() -> Self { Self { hash: 0 } } @@ -242,18 +253,25 @@ impl Hasher for PassHasher { panic!("cannot hash byte arrays using PassHasher"); } + #[inline] fn write_u64(&mut self, i: u64) { self.hash = i; } + #[inline] fn finish(&self) -> u64 { self.hash } } +/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing. pub type PreHashMap = hashbrown::HashMap, V, PassHash>; +/// Extension methods intended to add functionality to [`PreHashMap`]. pub trait PreHashMapExt { + /// Tries to get or insert the value for the given `key` using the pre-computed hash first. + /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert + /// the value returned by `func`. fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V; } From 3d56b37a1efb27fb05f3aad7603e677278d470c1 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 16 Feb 2022 15:04:38 -0800 Subject: [PATCH 6/9] clippy --- crates/bevy_utils/src/lib.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index deb9ef27359ee..6d1c8a4b1c9cb 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -215,7 +215,7 @@ impl Clone for Hashed { #[inline] fn clone(&self) -> Self { Self { - hash: self.hash.clone(), + hash: self.hash, value: self.value.clone(), marker: PhantomData, } @@ -236,18 +236,11 @@ impl BuildHasher for PassHash { } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct PassHasher { hash: u64, } -impl Default for PassHasher { - #[inline] - fn default() -> Self { - Self { hash: 0 } - } -} - impl Hasher for PassHasher { fn write(&mut self, _bytes: &[u8]) { panic!("cannot hash byte arrays using PassHasher"); From 6793774c18d67fceec506897e5973872ab9eaf3c Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 16 Feb 2022 15:46:46 -0800 Subject: [PATCH 7/9] fix doc tests --- crates/bevy_utils/src/lib.rs | 70 ++---------------------------------- 1 file changed, 3 insertions(+), 67 deletions(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 6d1c8a4b1c9cb..00e9afe616377 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -46,42 +46,10 @@ impl std::hash::BuildHasher for FixedState { /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// /// `aHash` is designed for performance and is NOT cryptographically secure. -/// -/// # Construction -/// -/// Users may be surprised when a `HashMap` cannot be constructed with `HashMap::new()`: -/// -/// ```compile_fail -/// # fn main() { -/// use bevy_utils::HashMap; -/// -/// // Produces an error like "no function or associated item named `new` found [...]" -/// let map: HashMap = HashMap::new(); -/// # } -/// ``` -/// -/// The standard library's [`HashMap::new`][hashbrown::HashMap::new] is -/// implemented only for `HashMap`s which use the -/// [`DefaultHasher`][std::collections::hash_map::DefaultHasher], so it's not -/// available for Bevy's `HashMap`. -/// -/// However, an empty `HashMap` can easily be constructed using the `Default` -/// implementation: -/// -/// ``` -/// # fn main() { -/// use bevy_utils::HashMap; -/// -/// // This works! -/// let map: HashMap = HashMap::default(); -/// assert!(map.is_empty()); -/// # } -/// ``` -/// /// [`aHash`]: https://github.com/tkaitchuck/aHash pub type HashMap = hashbrown::HashMap; -/// A stable std hash map implementing `aHash`, a high speed keyed hashing algorithm +/// A stable hash map implementing `aHash`, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// /// Unlike [`HashMap`] this has an iteration order that only depends on the order @@ -90,46 +58,14 @@ pub type HashMap = hashbrown::HashMap; /// `aHash` is designed for performance and is NOT cryptographically secure. pub type StableHashMap = hashbrown::HashMap; -/// A [`HashSet`][std::collections::HashSet] implementing [`aHash`], a high +/// A [`HashSet`][hashbrown::HashSet] implementing [`aHash`], a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// /// `aHash` is designed for performance and is NOT cryptographically secure. -/// -/// # Construction -/// -/// Users may be surprised when a `HashSet` cannot be constructed with `HashSet::new()`: -/// -/// ```compile_fail -/// # fn main() { -/// use bevy_utils::HashSet; -/// -/// // Produces an error like "no function or associated item named `new` found [...]" -/// let map: HashSet = HashSet::new(); -/// # } -/// ``` -/// -/// The standard library's [`HashSet::new`][std::collections::HashSet::new] is -/// implemented only for `HashSet`s which use the -/// [`DefaultHasher`][std::collections::hash_map::DefaultHasher], so it's not -/// available for Bevy's `HashSet`. -/// -/// However, an empty `HashSet` can easily be constructed using the `Default` -/// implementation: -/// -/// ``` -/// # fn main() { -/// use bevy_utils::HashSet; -/// -/// // This works! -/// let map: HashSet = HashSet::default(); -/// assert!(map.is_empty()); -/// # } -/// ``` -/// /// [`aHash`]: https://github.com/tkaitchuck/aHash pub type HashSet = hashbrown::HashSet; -/// A stable std hash set implementing `aHash`, a high speed keyed hashing algorithm +/// A stable hash set implementing `aHash`, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// /// Unlike [`HashSet`] this has an iteration order that only depends on the order From 5c4c601db01e553e653a802c2cc5516f97da21f5 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 17 Feb 2022 19:12:27 -0800 Subject: [PATCH 8/9] resolve comments --- crates/bevy_utils/src/lib.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 00e9afe616377..711dafa5c17d9 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -121,20 +121,12 @@ impl Deref for Hashed { } } -impl Hashed { - /// A faster version of [`PartialEq`] that first checks that `other`'s pre-computed hash - /// matches this value's pre-computed hash. For complex types, this can be much faster than - /// [`PartialEq`]. - #[inline] - pub fn fast_eq(&self, other: &Hashed) -> bool { - self.hash == other.hash && self.value.eq(&other.value) - } -} - impl PartialEq for Hashed { + /// A fast impl of [`PartialEq`] that first checks that `other`'s pre-computed hash + /// matches this value's pre-computed hash. #[inline] fn eq(&self, other: &Self) -> bool { - self.value.eq(&other.value) + self.hash == other.hash && self.value.eq(&other.value) } } @@ -179,7 +171,7 @@ pub struct PassHasher { impl Hasher for PassHasher { fn write(&mut self, _bytes: &[u8]) { - panic!("cannot hash byte arrays using PassHasher"); + panic!("can only hash u64 using PassHasher"); } #[inline] From 3ca884111e7d8f6517ce996aa843ce8b2206ef87 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 17 Feb 2022 19:25:26 -0800 Subject: [PATCH 9/9] tweak docs --- crates/bevy_utils/src/lib.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 711dafa5c17d9..d762e9577e7fb 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -42,40 +42,38 @@ impl std::hash::BuildHasher for FixedState { } } -/// A [`HashMap`][hashbrown::HashMap] implementing [`aHash`], a high +/// A [`HashMap`][hashbrown::HashMap] implementing aHash, a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// -/// `aHash` is designed for performance and is NOT cryptographically secure. -/// [`aHash`]: https://github.com/tkaitchuck/aHash +/// aHash is designed for performance and is NOT cryptographically secure. pub type HashMap = hashbrown::HashMap; -/// A stable hash map implementing `aHash`, a high speed keyed hashing algorithm +/// A stable hash map implementing aHash, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// /// Unlike [`HashMap`] this has an iteration order that only depends on the order /// of insertions and deletions and not a random source. /// -/// `aHash` is designed for performance and is NOT cryptographically secure. +/// aHash is designed for performance and is NOT cryptographically secure. pub type StableHashMap = hashbrown::HashMap; -/// A [`HashSet`][hashbrown::HashSet] implementing [`aHash`], a high +/// A [`HashSet`][hashbrown::HashSet] implementing aHash, a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// -/// `aHash` is designed for performance and is NOT cryptographically secure. -/// [`aHash`]: https://github.com/tkaitchuck/aHash +/// aHash is designed for performance and is NOT cryptographically secure. pub type HashSet = hashbrown::HashSet; -/// A stable hash set implementing `aHash`, a high speed keyed hashing algorithm +/// A stable hash set implementing aHash, a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// /// Unlike [`HashSet`] this has an iteration order that only depends on the order /// of insertions and deletions and not a random source. /// -/// `aHash` is designed for performance and is NOT cryptographically secure. +/// aHash is designed for performance and is NOT cryptographically secure. pub type StableHashSet = hashbrown::HashSet; /// A pre-hashed value of a specific type. Pre-hashing enables memoization of hashes that are expensive to compute. -/// It also enables accelerated comparisons using [`Hashed::fast_eq`]. +/// It also enables faster [`PartialEq`] comparisons by short circuiting on hash equality. /// See [`PassHash`] and [`PassHasher`] for a "pass through" [`BuildHasher`] and [`Hasher`] implementation /// designed to work with [`Hashed`] /// See [`PreHashMap`] for a hashmap pre-configured to use [`Hashed`] keys.