#![cfg_attr(features = "no_std", no_std)]
#![feature(integer_atomics)]
#![warn(missing_docs)]
extern crate core;
use core::fmt;
use core::sync::atomic::{self, AtomicU64, AtomicU8};
const ORDERING: atomic::Ordering = atomic::Ordering::Relaxed;
pub type MicroCache = Cache<[AtomicU64; 1]>;
pub type SmallCache = Cache<[AtomicU64; 2]>;
pub type MediumCache = Cache<[AtomicU64; 4]>;
pub type BigCache = Cache<[AtomicU64; 8]>;
pub type HugeCache = Cache<[AtomicU64; 32]>;
pub type DynamicCache = Cache<Box<[AtomicU64]>>;
#[cfg(not(features = "no_std"))]
pub fn create(lines: usize) -> DynamicCache {
let len = (lines + 63) / 64;
let mut vec = Vec::with_capacity(len);
for _ in 0..len {
vec.push(AtomicU64::default());
}
Cache::new(vec.into_boxed_slice())
}
#[derive(Default)]
pub struct Cache<B> {
bulks: B,
counter: AtomicU8,
}
impl<B: AsRef<[AtomicU64]>> Cache<B> {
pub fn new(bulks: B) -> Cache<B> {
Cache {
bulks: bulks,
counter: AtomicU8::default(),
}
}
pub fn touch(&self, n: usize) {
self.bulks.as_ref()[n / 64].fetch_or(1 << (n % 64), ORDERING);
}
pub fn trash(&self, n: usize) {
self.bulks.as_ref()[n / 64].fetch_and(!(1 << (n % 64)), ORDERING);
}
pub fn replace(&self) -> usize {
let counter = self.counter.fetch_add(1, ORDERING) as usize % self.bulks.as_ref().len();
let bulk = self.bulks.as_ref()[counter].compare_and_swap(!0, 0, ORDERING);
let ffz = (!bulk).trailing_zeros() % 64;
counter * 64 + ffz as usize
}
pub fn len(&self) -> usize {
self.bulks.as_ref().len() * 64
}
pub fn is_hot(&self, n: usize) -> bool {
self.bulks.as_ref()[n / 64].load(ORDERING) & (1 << (n % 64)) != 0
}
}
impl<B: AsRef<[AtomicU64]>> fmt::Debug for Cache<B> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for i in self.bulks.as_ref() {
write!(f, "{:b},", i.load(ORDERING))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(not(features = "no_std"))]
fn create() {
assert_eq!(super::create(10).len(), 64);
assert_eq!(super::create(64).len(), 64);
assert_eq!(super::create(63).len(), 64);
assert_eq!(super::create(1).len(), 64);
assert_eq!(super::create(65).len(), 128);
assert_eq!(super::create(128).len(), 128);
assert_eq!(super::create(129).len(), 192);
assert_eq!(super::create(192).len(), 192);
assert_eq!(super::create(256).len(), 256);
}
#[test]
fn default() {
let cache = super::BigCache::default();
for i in 0..512 {
assert!(!cache.is_hot(i));
}
}
#[test]
fn inflation() {
let cache = super::SmallCache::default();
cache.touch(0);
for i in 1..64 {
assert!(cache.is_hot(i - 1));
cache.touch(i);
}
assert_eq!(cache.replace(), 0);
for i in 0..64 {
assert!(!cache.is_hot(i));
}
}
#[test]
fn simple_replace() {
let cache = super::SmallCache::default();
assert_eq!(cache.replace(), 0);
assert_eq!(cache.replace(), 64);
assert_eq!(cache.replace(), 0);
assert_eq!(cache.replace(), 64);
cache.touch(0);
cache.touch(64);
assert_eq!(cache.replace(), 1);
assert_eq!(cache.replace(), 65);
cache.touch(1);
cache.touch(65);
assert_eq!(cache.replace(), 2);
assert_eq!(cache.replace(), 66);
}
#[test]
fn replace_and_touch() {
let cache = super::SmallCache::default();
for _ in 0..128 {
let r = cache.replace();
assert!(!cache.is_hot(r));
cache.touch(r);
assert!(cache.is_hot(r));
}
}
#[test]
fn replace_touch_trash() {
let cache = super::SmallCache::default();
for _ in 0..1000 {
let r = cache.replace();
cache.touch(r);
assert!(cache.is_hot(r));
cache.trash(r);
assert!(!cache.is_hot(r));
}
}
#[test]
fn replace_cold() {
let cache = super::SmallCache::default();
for i in 0..1000 {
let r = cache.replace();
assert!(!cache.is_hot(r));
if i % 2 == 0 {
cache.touch(r);
}
}
}
#[test]
fn trash_cold() {
let cache = super::SmallCache::default();
for i in 0..128 {
cache.trash(i);
assert!(!cache.is_hot(i));
}
}
#[test]
fn cache_sizes() {
let a = super::MicroCache::default();
let b = super::SmallCache::default();
let c = super::MediumCache::default();
let d = super::BigCache::default();
let e = super::HugeCache::default();
assert!(a.len() < b.len());
assert!(b.len() < c.len());
assert!(c.len() < d.len());
assert!(d.len() < e.len());
}
#[test]
#[should_panic]
fn line_oob() {
let cache = super::SmallCache::default();
cache.touch(128);
}
}