From 3f455a8bc6bba9f95807182a53e149721ad9c205 Mon Sep 17 00:00:00 2001 From: David Roundy Date: Sun, 18 Oct 2026 12:13:16 -0700 Subject: [PATCH] Share one type_bucket function among the per-type containers container.rs, append_container.rs and typearena.rs each defined the same hash_of_type function, with its own HasherForTypeId, and their own 32-bucket constant. Move both into a type_bucket module, so how a type picks its bucket is written once. Each caller hashes the same type as before, so every type stays in the same bucket. Co-Authored-By: Claude Opus 5.5 --- src/append_container.rs | 48 ++++-------------------------------- src/container.rs | 53 ++++------------------------------------ src/lib.rs | 7 ++++++ src/type_bucket.rs | 54 +++++++++++++++++++++++++++++++++++++++++ src/typearena.rs | 47 +++++------------------------------ 5 files changed, 77 insertions(+), 132 deletions(-) create mode 100644 src/type_bucket.rs diff --git a/src/append_container.rs b/src/append_container.rs index e57f805..3911ed8 100644 --- a/src/append_container.rs +++ b/src/append_container.rs @@ -1,9 +1,7 @@ +use crate::type_bucket::{type_bucket, TYPE_BUCKETS}; use alloc::boxed::Box; use append_only_vec::AppendOnlyVec; -use core::{ - any::{Any, TypeId}, - hash::{Hash, Hasher}, -}; +use core::any::Any; #[cfg(not(any(feature = "std", feature = "spin")))] compile_error!( @@ -29,9 +27,8 @@ impl TypeHolderSync { } } -const INTERN_CONTAINER_COUNT: usize = 32; pub struct Arena { - containers: [TypeHolderSync; INTERN_CONTAINER_COUNT], + containers: [TypeHolderSync; TYPE_BUCKETS], } impl Arena { @@ -39,7 +36,7 @@ impl Arena { #[allow(clippy::declare_interior_mutable_const)] const CONTAINER: TypeHolderSync = TypeHolderSync::new(); Arena { - containers: [CONTAINER; INTERN_CONTAINER_COUNT], + containers: [CONTAINER; TYPE_BUCKETS], } } @@ -47,42 +44,7 @@ impl Arena { where T: Any + Send + Sync + Default, { - // Compute the hash of the type. - fn hash_of_type() -> u64 { - // We use very simple hasher, because it is optimized away to a constant: - // https://rust.godbolt.org/z/4T1fa4GGs - // which is not true for using `DefaultHasher`: - // https://rust.godbolt.org/z/qKar1WKfz - struct HasherForTypeId { - hash: u64, - } - - impl Hasher for HasherForTypeId { - fn write(&mut self, bytes: &[u8]) { - // Hash for type only calls `write_u64` once, - // but handle this case explicitly to make sure - // this code doesn't break if stdlib internals change. - - for byte in bytes { - self.hash = self.hash.wrapping_mul(31).wrapping_add(*byte as u64); - } - } - - fn write_u64(&mut self, v: u64) { - self.hash = v; - } - - fn finish(&self) -> u64 { - self.hash - } - } - - let mut hasher = HasherForTypeId { hash: 0 }; - TypeId::of::().hash(&mut hasher); - hasher.finish() - } - - self.containers[hash_of_type::() as usize % INTERN_CONTAINER_COUNT].get_type() + self.containers[type_bucket::()].get_type() } } diff --git a/src/container.rs b/src/container.rs index 1938b1c..e7968b0 100644 --- a/src/container.rs +++ b/src/container.rs @@ -1,9 +1,6 @@ +use crate::type_bucket::{type_bucket, TYPE_BUCKETS}; use alloc::{boxed::Box, vec::Vec}; -use core::{ - any::{Any, TypeId}, - hash::{Hash, Hasher}, - ops::DerefMut, -}; +use core::{any::Any, ops::DerefMut}; #[cfg(not(any(feature = "std", feature = "spin")))] compile_error!( @@ -62,9 +59,8 @@ impl TypeHolderSendCell { } } -const INTERN_CONTAINER_COUNT: usize = 32; pub struct Arena { - containers: [TypeHolderSendCell; INTERN_CONTAINER_COUNT], + containers: [TypeHolderSendCell; TYPE_BUCKETS], } impl Arena { @@ -72,7 +68,7 @@ impl Arena { #[allow(clippy::declare_interior_mutable_const)] const CONTAINER: TypeHolderSendCell = TypeHolderSendCell::new(); Arena { - containers: [CONTAINER; INTERN_CONTAINER_COUNT], + containers: [CONTAINER; TYPE_BUCKETS], } } @@ -81,46 +77,7 @@ impl Arena { F: FnOnce(&mut T) -> R, T: Any + Send + Default, { - // Compute the hash of the type. - fn hash_of_type() -> u64 { - // We use very simple hasher, because it is optimized away to a constant: - // https://rust.godbolt.org/z/4T1fa4GGs - // which is not true for using `DefaultHasher`: - // https://rust.godbolt.org/z/qKar1WKfz - struct HasherForTypeId { - hash: u64, - } - - impl Hasher for HasherForTypeId { - fn write(&mut self, bytes: &[u8]) { - // Hash for type only calls `write_u64` once, - // but handle this case explicitly to make sure - // this code doesn't break if stdlib internals change. - - for byte in bytes { - self.hash = self.hash.wrapping_mul(31).wrapping_add(*byte as u64); - } - } - - fn write_u64(&mut self, v: u64) { - self.hash = v; - } - - fn finish(&self) -> u64 { - self.hash - } - } - - let mut hasher = HasherForTypeId { hash: 0 }; - TypeId::of::().hash(&mut hasher); - hasher.finish() - } - - f( - self.containers[hash_of_type::() as usize % INTERN_CONTAINER_COUNT] - .get_mut() - .get_type_mut(), - ) + f(self.containers[type_bucket::()].get_mut().get_type_mut()) } } diff --git a/src/lib.rs b/src/lib.rs index ec5347e..2fa6c4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,13 @@ extern crate alloc; mod boxedset; +#[cfg(any( + feature = "intern", + feature = "intern32", + feature = "_experimental-new-intern" +))] +mod type_bucket; + #[cfg(feature = "_experimental-new-intern")] mod typearena; diff --git a/src/type_bucket.rs b/src/type_bucket.rs new file mode 100644 index 0000000..876b958 --- /dev/null +++ b/src/type_bucket.rs @@ -0,0 +1,54 @@ +//! Spreading per-type containers over a fixed number of buckets. +//! +//! The interners keep one container per interned type, found by `TypeId`. To +//! avoid searching every type's container, they are spread over +//! [`TYPE_BUCKETS`] buckets, and [`type_bucket`] says which one a type is in. + +use core::any::TypeId; +use core::hash::{Hash, Hasher}; + +/// How many buckets the per-type containers are spread over. +pub(crate) const TYPE_BUCKETS: usize = 32; + +/// Which of the [`TYPE_BUCKETS`] buckets holds the container for `T`. +#[inline] +pub(crate) fn type_bucket() -> usize { + // We use very simple hasher, because it is optimized away to a constant: + // https://rust.godbolt.org/z/4T1fa4GGs + // which is not true for using `DefaultHasher`: + // https://rust.godbolt.org/z/qKar1WKfz + struct HasherForTypeId { + hash: u64, + } + + impl Hasher for HasherForTypeId { + fn write(&mut self, bytes: &[u8]) { + // Hash for type only calls `write_u64` once, + // but handle this case explicitly to make sure + // this code doesn't break if stdlib internals change. + + for byte in bytes { + self.hash = self.hash.wrapping_mul(31).wrapping_add(*byte as u64); + } + } + + fn write_u64(&mut self, v: u64) { + self.hash = v; + } + + fn finish(&self) -> u64 { + self.hash + } + } + + let mut hasher = HasherForTypeId { hash: 0 }; + TypeId::of::().hash(&mut hasher); + hasher.finish() as usize % TYPE_BUCKETS +} + +#[test] +fn same_type_same_bucket() { + assert_eq!(type_bucket::(), type_bucket::()); + assert!(type_bucket::() < TYPE_BUCKETS); + assert!(type_bucket::() < TYPE_BUCKETS); +} diff --git a/src/typearena.rs b/src/typearena.rs index 1e857f7..892717a 100644 --- a/src/typearena.rs +++ b/src/typearena.rs @@ -1,8 +1,9 @@ #![deny(missing_docs)] +use crate::type_bucket::{type_bucket, TYPE_BUCKETS}; use alloc::boxed::Box; use append_only_vec::AppendOnlyVec; use core::{ - any::{Any, TypeId}, + any::Any, hash::{Hash, Hasher}, }; use std::{ffi::OsStr, path::Path, println}; @@ -15,66 +16,30 @@ use alloc::{ struct AnySend(Box); -const CONTAINER_COUNT: usize = 32; #[allow(clippy::declare_interior_mutable_const)] const CONTAINER: AppendOnlyVec = AppendOnlyVec::new(); -static CONTAINERS: [AppendOnlyVec; CONTAINER_COUNT] = [CONTAINER; CONTAINER_COUNT]; +static CONTAINERS: [AppendOnlyVec; TYPE_BUCKETS] = [CONTAINER; TYPE_BUCKETS]; pub fn with_mutex_hashset(f: F) -> R where F: FnOnce(&mut HashSet) -> R, T: Any + Send + Sync, { - // Compute the hash of the type. - fn hash_of_type() -> usize { - // We use very simple hasher, because it is optimized away to a constant: - // https://rust.godbolt.org/z/4T1fa4GGs - // which is not true for using `DefaultHasher`: - // https://rust.godbolt.org/z/qKar1WKfz - struct HasherForTypeId { - hash: u64, - } - - impl Hasher for HasherForTypeId { - fn write(&mut self, bytes: &[u8]) { - // Hash for type only calls `write_u64` once, - // but handle this case explicitly to make sure - // this code doesn't break if stdlib internals change. - - for byte in bytes { - self.hash = self.hash.wrapping_mul(31).wrapping_add(*byte as u64); - } - } - - fn write_u64(&mut self, v: u64) { - self.hash = v; - } - - fn finish(&self) -> u64 { - self.hash - } - } - - let mut hasher = HasherForTypeId { hash: 0 }; - TypeId::of::>>().hash(&mut hasher); - hasher.hash as usize - } - use core::ops::DerefMut; - for v in CONTAINERS[hash_of_type::() % CONTAINER_COUNT].iter() { + for v in CONTAINERS[type_bucket::>>()].iter() { if let Some(m) = v.0.downcast_ref::>>() { let mut m = m.lock().unwrap(); return f(m.deref_mut()); } } let default: Mutex> = Mutex::new(HashSet::new()); - CONTAINERS[hash_of_type::() % CONTAINER_COUNT].push(AnySend(Box::new(default))); + CONTAINERS[type_bucket::>>()].push(AnySend(Box::new(default))); // We now search through the AppendOnlyVec, just in case there was a race // and two things of the same type got pushed sumultaneously. If that // happens, we'll just use the first one. It's a little hokey leaving this // race condition here, but at worst it should just slow down access to a // different type. - for v in CONTAINERS[hash_of_type::() % CONTAINER_COUNT].iter() { + for v in CONTAINERS[type_bucket::>>()].iter() { if let Some(m) = v.0.downcast_ref::>>() { let mut m = m.lock().unwrap(); return f(m.deref_mut());