use std::sync::atomic::AtomicU64;
use crate::stores::BuildError;
pub(crate) const CACHE_LINE: usize = 128;
const _: () = assert!(
CACHE_LINE == 128,
"CachePadded repr(align) literal must match CACHE_LINE"
);
#[repr(align(128))]
pub(crate) struct CachePadded<T>(pub T);
impl<T> std::ops::Deref for CachePadded<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for CachePadded<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
pub(crate) struct Shard<S> {
pub lock: parking_lot::RwLock<S>,
pub hits: AtomicU64,
pub misses: AtomicU64,
}
impl<S> Shard<S> {
pub fn new(store: S) -> Self {
Self {
lock: parking_lot::RwLock::new(store),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
}
}
}
pub(crate) fn default_shard_count() -> usize {
let count = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
.saturating_mul(4);
count.clamp(8, 1024).next_power_of_two()
}
pub(crate) fn checked_shard_count(shards: Option<usize>) -> Result<usize, BuildError> {
if let Some(0) = shards {
return Err(BuildError::InvalidValue {
field: "shards",
reason: "shard count must be >= 1",
});
}
shards
.unwrap_or_else(default_shard_count)
.checked_next_power_of_two()
.ok_or(BuildError::InvalidValue {
field: "shards",
reason: "rounded shard count overflows usize",
})
}
#[inline]
pub(crate) fn shard_index(hash: u64, mask: usize) -> usize {
(hash >> 32) as usize & mask
}
pub trait ShardHasher<K>: Send + Sync + 'static {
fn shard_hash(&self, key: &K) -> u64;
}
#[derive(Clone)]
pub struct DefaultShardHasher(
#[cfg(feature = "ahash")] ahash::RandomState,
#[cfg(not(feature = "ahash"))] std::collections::hash_map::RandomState,
);
impl Default for DefaultShardHasher {
fn default() -> Self {
Self::new()
}
}
impl DefaultShardHasher {
#[must_use]
pub fn new() -> Self {
#[cfg(feature = "ahash")]
{
Self(ahash::RandomState::new())
}
#[cfg(not(feature = "ahash"))]
{
Self(std::collections::hash_map::RandomState::new())
}
}
}
impl<K: std::hash::Hash> ShardHasher<K> for DefaultShardHasher {
fn shard_hash(&self, key: &K) -> u64 {
use std::hash::BuildHasher;
BuildHasher::hash_one(&self.0, key)
}
}
mod expiring;
mod expiring_lru;
mod lru;
mod unbound;
#[cfg(feature = "time_stores")]
mod lru_ttl;
#[cfg(feature = "time_stores")]
mod ttl;
pub use expiring::{ShardedExpiringCache, ShardedExpiringCacheBase, ShardedExpiringCacheBuilder};
pub use expiring_lru::{
ShardedExpiringLruCache, ShardedExpiringLruCacheBase, ShardedExpiringLruCacheBuilder,
};
pub use lru::{ShardedLruCache, ShardedLruCacheBase, ShardedLruCacheBuilder};
pub use unbound::{ShardedCache, ShardedCacheBase, ShardedCacheBuilder};
#[cfg(feature = "time_stores")]
pub use ttl::{ShardedTtlCache, ShardedTtlCacheBase, ShardedTtlCacheBuilder};
#[cfg(feature = "time_stores")]
pub use lru_ttl::{ShardedLruTtlCache, ShardedLruTtlCacheBase, ShardedLruTtlCacheBuilder};
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{align_of, size_of};
#[test]
fn cache_padded_is_aligned() {
assert_eq!(align_of::<CachePadded<u8>>(), CACHE_LINE);
assert_eq!(size_of::<CachePadded<u8>>() % CACHE_LINE, 0);
}
#[test]
fn default_shard_hasher_works() {
let h = DefaultShardHasher::new();
let v1 = h.shard_hash(&42u64);
let v2 = h.shard_hash(&42u64);
assert_eq!(v1, v2);
let v3 = h.shard_hash(&43u64);
assert_ne!(v1, v3);
}
}