Make Sharded an enum and specialize it for the single thread case
This commit is contained in:
parent
c1699a79a6
commit
c737c62e70
1 changed files with 30 additions and 36 deletions
|
@ -1,31 +1,25 @@
|
||||||
use crate::fx::{FxHashMap, FxHasher};
|
use crate::fx::{FxHashMap, FxHasher};
|
||||||
#[cfg(parallel_compiler)]
|
#[cfg(parallel_compiler)]
|
||||||
use crate::sync::is_dyn_thread_safe;
|
use crate::sync::{is_dyn_thread_safe, CacheAligned};
|
||||||
use crate::sync::{CacheAligned, Lock, LockGuard};
|
use crate::sync::{Lock, LockGuard};
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::hash_map::RawEntryMut;
|
use std::collections::hash_map::RawEntryMut;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
#[cfg(parallel_compiler)]
|
|
||||||
// 32 shards is sufficient to reduce contention on an 8-core Ryzen 7 1700,
|
// 32 shards is sufficient to reduce contention on an 8-core Ryzen 7 1700,
|
||||||
// but this should be tested on higher core count CPUs. How the `Sharded` type gets used
|
// but this should be tested on higher core count CPUs. How the `Sharded` type gets used
|
||||||
// may also affect the ideal number of shards.
|
// may also affect the ideal number of shards.
|
||||||
const SHARD_BITS: usize = 5;
|
const SHARD_BITS: usize = if cfg!(parallel_compiler) { 5 } else { 0 };
|
||||||
|
|
||||||
#[cfg(not(parallel_compiler))]
|
|
||||||
const SHARD_BITS: usize = 0;
|
|
||||||
|
|
||||||
pub const SHARDS: usize = 1 << SHARD_BITS;
|
pub const SHARDS: usize = 1 << SHARD_BITS;
|
||||||
|
|
||||||
/// An array of cache-line aligned inner locked structures with convenience methods.
|
/// An array of cache-line aligned inner locked structures with convenience methods.
|
||||||
pub struct Sharded<T> {
|
/// A single field is used when the compiler uses only one thread.
|
||||||
/// This mask is used to ensure that accesses are inbounds of `shards`.
|
pub enum Sharded<T> {
|
||||||
/// When dynamic thread safety is off, this field is set to 0 causing only
|
Single(Lock<T>),
|
||||||
/// a single shard to be used for greater cache efficiency.
|
|
||||||
#[cfg(parallel_compiler)]
|
#[cfg(parallel_compiler)]
|
||||||
mask: usize,
|
Shards(Box<[CacheAligned<Lock<T>>; SHARDS]>),
|
||||||
shards: [CacheAligned<Lock<T>>; SHARDS],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Default> Default for Sharded<T> {
|
impl<T: Default> Default for Sharded<T> {
|
||||||
|
@ -38,29 +32,14 @@ impl<T: Default> Default for Sharded<T> {
|
||||||
impl<T> Sharded<T> {
|
impl<T> Sharded<T> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(mut value: impl FnMut() -> T) -> Self {
|
pub fn new(mut value: impl FnMut() -> T) -> Self {
|
||||||
Sharded {
|
|
||||||
#[cfg(parallel_compiler)]
|
#[cfg(parallel_compiler)]
|
||||||
mask: if is_dyn_thread_safe() { SHARDS - 1 } else { 0 },
|
if is_dyn_thread_safe() {
|
||||||
shards: [(); SHARDS].map(|()| CacheAligned(Lock::new(value()))),
|
return Sharded::Shards(Box::new(
|
||||||
}
|
[(); SHARDS].map(|()| CacheAligned(Lock::new(value()))),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
Sharded::Single(Lock::new(value()))
|
||||||
fn mask(&self) -> usize {
|
|
||||||
#[cfg(parallel_compiler)]
|
|
||||||
{
|
|
||||||
if SHARDS == 1 { 0 } else { self.mask }
|
|
||||||
}
|
|
||||||
#[cfg(not(parallel_compiler))]
|
|
||||||
{
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
// `self.mask` is always one below the used shard count
|
|
||||||
self.mask() + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shard is selected by hashing `val` with `FxHasher`.
|
/// The shard is selected by hashing `val` with `FxHasher`.
|
||||||
|
@ -75,9 +54,24 @@ impl<T> Sharded<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_shard_by_index(&self, i: usize) -> &Lock<T> {
|
pub fn get_shard_by_index(&self, _i: usize) -> &Lock<T> {
|
||||||
// SAFETY: The index get ANDed with the mask, ensuring it is always inbounds.
|
match self {
|
||||||
unsafe { &self.shards.get_unchecked(i & self.mask()).0 }
|
Self::Single(single) => &single,
|
||||||
|
#[cfg(parallel_compiler)]
|
||||||
|
Self::Shards(shards) => {
|
||||||
|
// SAFETY: The index gets ANDed with the shard mask, ensuring it is always inbounds.
|
||||||
|
unsafe { &shards.get_unchecked(_i & (SHARDS - 1)).0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Single(..) => 1,
|
||||||
|
#[cfg(parallel_compiler)]
|
||||||
|
Self::Shards(..) => SHARDS,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lock_shards(&self) -> Vec<LockGuard<'_, T>> {
|
pub fn lock_shards(&self) -> Vec<LockGuard<'_, T>> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue