Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 5 additions & 43 deletions src/append_container.rs
Original file line number Diff line number Diff line change
@@ -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!(
Expand All @@ -29,60 +27,24 @@ impl TypeHolderSync {
}
}

const INTERN_CONTAINER_COUNT: usize = 32;
pub struct Arena {
containers: [TypeHolderSync; INTERN_CONTAINER_COUNT],
containers: [TypeHolderSync; TYPE_BUCKETS],
}

impl Arena {
pub const fn new() -> Self {
#[allow(clippy::declare_interior_mutable_const)]
const CONTAINER: TypeHolderSync = TypeHolderSync::new();
Arena {
containers: [CONTAINER; INTERN_CONTAINER_COUNT],
containers: [CONTAINER; TYPE_BUCKETS],
}
}

pub fn get<T>(&self) -> &T
where
T: Any + Send + Sync + Default,
{
// Compute the hash of the type.
fn hash_of_type<T: 'static>() -> 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::<T>().hash(&mut hasher);
hasher.finish()
}

self.containers[hash_of_type::<T>() as usize % INTERN_CONTAINER_COUNT].get_type()
self.containers[type_bucket::<T>()].get_type()
}
}

Expand Down
53 changes: 5 additions & 48 deletions src/container.rs
Original file line number Diff line number Diff line change
@@ -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!(
Expand Down Expand Up @@ -62,17 +59,16 @@ impl TypeHolderSendCell {
}
}

const INTERN_CONTAINER_COUNT: usize = 32;
pub struct Arena {
containers: [TypeHolderSendCell; INTERN_CONTAINER_COUNT],
containers: [TypeHolderSendCell; TYPE_BUCKETS],
}

impl Arena {
pub const fn new() -> Self {
#[allow(clippy::declare_interior_mutable_const)]
const CONTAINER: TypeHolderSendCell = TypeHolderSendCell::new();
Arena {
containers: [CONTAINER; INTERN_CONTAINER_COUNT],
containers: [CONTAINER; TYPE_BUCKETS],
}
}

Expand All @@ -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<T: 'static>() -> 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::<T>().hash(&mut hasher);
hasher.finish()
}

f(
self.containers[hash_of_type::<T>() as usize % INTERN_CONTAINER_COUNT]
.get_mut()
.get_type_mut(),
)
f(self.containers[type_bucket::<T>()].get_mut().get_type_mut())
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
54 changes: 54 additions & 0 deletions src/type_bucket.rs
Original file line number Diff line number Diff line change
@@ -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<T: 'static>() -> 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::<T>().hash(&mut hasher);
hasher.finish() as usize % TYPE_BUCKETS
}

#[test]
fn same_type_same_bucket() {
assert_eq!(type_bucket::<u32>(), type_bucket::<u32>());
assert!(type_bucket::<u32>() < TYPE_BUCKETS);
assert!(type_bucket::<alloc::string::String>() < TYPE_BUCKETS);
}
47 changes: 6 additions & 41 deletions src/typearena.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -15,66 +16,30 @@ use alloc::{

struct AnySend(Box<dyn Any + Send + Sync>);

const CONTAINER_COUNT: usize = 32;
#[allow(clippy::declare_interior_mutable_const)]
const CONTAINER: AppendOnlyVec<AnySend> = AppendOnlyVec::new();
static CONTAINERS: [AppendOnlyVec<AnySend>; CONTAINER_COUNT] = [CONTAINER; CONTAINER_COUNT];
static CONTAINERS: [AppendOnlyVec<AnySend>; TYPE_BUCKETS] = [CONTAINER; TYPE_BUCKETS];

pub fn with_mutex_hashset<F, T, R>(f: F) -> R
where
F: FnOnce(&mut HashSet<T>) -> R,
T: Any + Send + Sync,
{
// Compute the hash of the type.
fn hash_of_type<T: 'static>() -> 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::<Mutex<HashSet<T>>>().hash(&mut hasher);
hasher.hash as usize
}

use core::ops::DerefMut;
for v in CONTAINERS[hash_of_type::<T>() % CONTAINER_COUNT].iter() {
for v in CONTAINERS[type_bucket::<Mutex<HashSet<T>>>()].iter() {
if let Some(m) = v.0.downcast_ref::<Mutex<HashSet<T>>>() {
let mut m = m.lock().unwrap();
return f(m.deref_mut());
}
}
let default: Mutex<HashSet<T>> = Mutex::new(HashSet::new());
CONTAINERS[hash_of_type::<T>() % CONTAINER_COUNT].push(AnySend(Box::new(default)));
CONTAINERS[type_bucket::<Mutex<HashSet<T>>>()].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::<T>() % CONTAINER_COUNT].iter() {
for v in CONTAINERS[type_bucket::<Mutex<HashSet<T>>>()].iter() {
if let Some(m) = v.0.downcast_ref::<Mutex<HashSet<T>>>() {
let mut m = m.lock().unwrap();
return f(m.deref_mut());
Expand Down
Loading