pub type Slot = u32;
const NIL: Slot = Slot::MAX;
#[derive(Clone, Copy)]
struct Link {
prev: Slot,
next: Slot,
freq: u8,
queue: u8,
}
const OUT: u8 = 0;
const SMALL: u8 = 1;
const MAIN: u8 = 2;
impl Link {
const fn empty() -> Link {
Link {
prev: NIL,
next: NIL,
freq: 0,
queue: OUT,
}
}
}
#[derive(Clone, Copy)]
struct Queue {
head: Slot,
tail: Slot,
len: usize,
}
impl Queue {
const fn new() -> Queue {
Queue {
head: NIL,
tail: NIL,
len: 0,
}
}
fn push_head(&mut self, links: &mut [Link], slot: Slot, queue: u8) {
let i = slot as usize;
links[i].prev = NIL;
links[i].next = self.head;
links[i].queue = queue;
if self.head != NIL {
links[self.head as usize].prev = slot;
} else {
self.tail = slot;
}
self.head = slot;
self.len += 1;
}
fn unlink(&mut self, links: &mut [Link], slot: Slot) {
let i = slot as usize;
let (prev, next) = (links[i].prev, links[i].next);
if prev != NIL {
links[prev as usize].next = next;
} else {
self.head = next;
}
if next != NIL {
links[next as usize].prev = prev;
} else {
self.tail = prev;
}
links[i] = Link::empty();
self.len -= 1;
}
}
pub struct Sieve {
links: Vec<Link>,
q: Queue,
hand: Slot,
}
impl Sieve {
pub fn new(slots: usize) -> Sieve {
assert!(slots < NIL as usize, "a slot number has to fit in a u32");
Sieve {
links: vec![Link::empty(); slots],
q: Queue::new(),
hand: NIL,
}
}
pub fn len(&self) -> usize {
self.q.len
}
pub fn is_empty(&self) -> bool {
self.q.len == 0
}
pub fn contains(&self, slot: Slot) -> bool {
self.links[slot as usize].queue != OUT
}
pub fn insert(&mut self, slot: Slot) {
if self.contains(slot) {
return;
}
self.q.push_head(&mut self.links, slot, MAIN);
}
pub fn touch(&mut self, slot: Slot) {
let link = &mut self.links[slot as usize];
if link.queue != OUT {
link.freq = 1;
}
}
pub fn remove(&mut self, slot: Slot) {
if !self.contains(slot) {
return;
}
if self.hand == slot {
self.hand = self.links[slot as usize].prev;
}
self.q.unlink(&mut self.links, slot);
}
pub fn demote(&mut self) -> Option<Slot> {
if self.q.len == 0 {
return None;
}
let mut at = if self.hand == NIL {
self.q.tail
} else {
self.hand
};
loop {
if self.links[at as usize].freq == 0 {
let prev = self.links[at as usize].prev;
self.hand = prev;
self.q.unlink(&mut self.links, at);
return Some(at);
}
self.links[at as usize].freq = 0;
at = self.links[at as usize].prev;
if at == NIL {
at = self.q.tail;
}
}
}
}
pub struct S3Fifo {
links: Vec<Link>,
small: Queue,
main: Queue,
ghost: Ghost,
small_cap: usize,
}
impl S3Fifo {
pub const MAX_FREQ: u8 = 3;
pub fn new(slots: usize, capacity: usize) -> S3Fifo {
assert!(slots < NIL as usize, "a slot number has to fit in a u32");
assert!(capacity > 0, "a cache with no room is not a cache");
S3Fifo {
links: vec![Link::empty(); slots],
small: Queue::new(),
main: Queue::new(),
ghost: Ghost::new(capacity),
small_cap: (capacity / 10).max(1),
}
}
pub fn len(&self) -> usize {
self.small.len + self.main.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn contains(&self, slot: Slot) -> bool {
self.links[slot as usize].queue != OUT
}
pub fn insert(&mut self, slot: Slot, finger: u64) {
if self.contains(slot) {
return;
}
if self.ghost.forget(finger) {
self.main.push_head(&mut self.links, slot, MAIN);
} else {
self.small.push_head(&mut self.links, slot, SMALL);
}
}
pub fn touch(&mut self, slot: Slot) {
let link = &mut self.links[slot as usize];
if link.queue != OUT {
link.freq = (link.freq + 1).min(Self::MAX_FREQ);
}
}
pub fn remove(&mut self, slot: Slot) {
match self.links[slot as usize].queue {
SMALL => self.small.unlink(&mut self.links, slot),
MAIN => self.main.unlink(&mut self.links, slot),
_ => {}
}
}
pub fn demote(&mut self, finger_of: impl Fn(Slot) -> u64) -> Option<Slot> {
loop {
if self.small.len > self.small_cap && self.small.len > 0 {
let slot = self.small.tail;
let freq = self.links[slot as usize].freq;
self.small.unlink(&mut self.links, slot);
if freq > 1 {
self.main.push_head(&mut self.links, slot, MAIN);
continue;
}
self.ghost.remember(finger_of(slot));
return Some(slot);
}
if self.main.len > 0 {
let slot = self.main.tail;
let freq = self.links[slot as usize].freq;
if freq > 0 {
self.main.unlink(&mut self.links, slot);
self.main.push_head(&mut self.links, slot, MAIN);
self.links[slot as usize].freq = freq - 1;
continue;
}
self.main.unlink(&mut self.links, slot);
return Some(slot);
}
if self.small.len > 0 {
let slot = self.small.tail;
self.small.unlink(&mut self.links, slot);
self.ghost.remember(finger_of(slot));
return Some(slot);
}
return None;
}
}
}
struct Ghost {
ring: Vec<u64>,
at: usize,
}
const NO_FINGER: u64 = 0;
impl Ghost {
fn new(capacity: usize) -> Ghost {
Ghost {
ring: vec![NO_FINGER; capacity.min(4096)],
at: 0,
}
}
fn remember(&mut self, finger: u64) {
if self.ring.is_empty() {
return;
}
self.ring[self.at] = clean(finger);
self.at = (self.at + 1) % self.ring.len();
}
fn forget(&mut self, finger: u64) -> bool {
let want = clean(finger);
for slot in &mut self.ring {
if *slot == want {
*slot = NO_FINGER;
return true;
}
}
false
}
}
fn clean(finger: u64) -> u64 {
if finger == NO_FINGER { 1 } else { finger }
}
pub struct Doorkeeper {
bits: Vec<u64>,
seen: usize,
window: usize,
}
impl Doorkeeper {
const BITS_PER_KEY: usize = 8;
pub fn new(window: usize) -> Doorkeeper {
let window = window.max(64);
let words = (window * Self::BITS_PER_KEY).div_ceil(64);
Doorkeeper {
bits: vec![0; words],
seen: 0,
window,
}
}
pub fn admit(&mut self, finger: u64) -> bool {
let (a, b) = self.probes(finger);
let had = self.get(a) && self.get(b);
self.set(a);
self.set(b);
if !had {
self.seen += 1;
if self.seen >= self.window {
self.reset();
}
}
had
}
pub fn reset(&mut self) {
self.bits.fill(0);
self.seen = 0;
}
pub fn memory_bytes(&self) -> usize {
self.bits.len() * 8
}
fn probes(&self, finger: u64) -> (usize, usize) {
let n = self.bits.len() * 64;
let a = (finger >> 32) as usize % n;
let b = (finger & 0xffff_ffff) as usize % n;
(a, b)
}
fn get(&self, at: usize) -> bool {
self.bits[at / 64] & (1 << (at % 64)) != 0
}
fn set(&mut self, at: usize) {
self.bits[at / 64] |= 1 << (at % 64);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn finger(slot: Slot) -> u64 {
u64::from(slot).wrapping_mul(0x9e37_79b9_7f4a_7c15) | 1
}
#[test]
fn sieve_takes_the_oldest_untouched_entry() {
let mut s = Sieve::new(8);
for slot in 0..4 {
s.insert(slot);
}
assert_eq!(s.demote(), Some(0));
assert_eq!(s.demote(), Some(1));
assert_eq!(s.len(), 2);
}
#[test]
fn sieve_spares_an_entry_that_was_used_and_takes_it_next_time() {
let mut s = Sieve::new(8);
for slot in 0..3 {
s.insert(slot);
}
s.touch(0);
assert_eq!(s.demote(), Some(1));
assert_eq!(s.demote(), Some(2));
assert_eq!(s.demote(), Some(0));
assert_eq!(s.demote(), None);
}
#[test]
fn sieve_does_not_move_a_survivor_to_the_head() {
let mut s = Sieve::new(8);
for slot in 0..3 {
s.insert(slot);
}
s.touch(0);
s.demote();
assert_eq!(s.demote(), Some(2));
assert_eq!(s.demote(), Some(0));
}
#[test]
fn sieve_removes_the_hand_it_was_pointing_at() {
let mut s = Sieve::new(8);
for slot in 0..4 {
s.insert(slot);
}
s.touch(1);
s.touch(0);
assert_eq!(s.demote(), Some(2));
s.remove(1);
assert_eq!(s.demote(), Some(3));
assert_eq!(s.demote(), Some(0));
assert!(s.is_empty());
}
#[test]
fn s3_fifo_throws_out_what_was_only_seen_once() {
let mut s = S3Fifo::new(64, 20);
for slot in 0..5 {
s.insert(slot, finger(slot));
}
assert_eq!(s.demote(finger), Some(0));
assert_eq!(s.demote(finger), Some(1));
}
#[test]
fn s3_fifo_promotes_what_was_asked_for_twice() {
let mut s = S3Fifo::new(64, 20);
for slot in 0..5 {
s.insert(slot, finger(slot));
}
s.touch(0);
s.touch(0);
assert_eq!(s.demote(finger), Some(1));
assert!(s.contains(0));
}
#[test]
fn s3_fifo_sends_a_returning_key_straight_to_the_main_queue() {
let mut s = S3Fifo::new(64, 20);
for slot in 0..5 {
s.insert(slot, finger(slot));
}
assert_eq!(s.demote(finger), Some(0));
s.insert(0, finger(0));
assert_eq!(s.demote(finger), Some(1));
assert!(s.contains(0));
}
#[test]
fn s3_fifo_only_lets_the_ghost_queue_promote_once() {
let mut s = S3Fifo::new(64, 20);
for slot in 0..5 {
s.insert(slot, finger(slot));
}
assert_eq!(s.demote(finger), Some(0));
s.insert(0, finger(0));
s.remove(0);
s.insert(0, finger(0));
assert_eq!(s.demote(finger), Some(1));
assert_eq!(s.demote(finger), Some(2));
}
#[test]
fn s3_fifo_second_chance_spends_the_count_rather_than_looping() {
let mut s = S3Fifo::new(64, 4);
for slot in 0..3 {
s.insert(slot, finger(slot));
s.touch(slot);
s.touch(slot);
}
let first = s.demote(finger).expect("a victim");
assert!(first < 3);
assert_eq!(s.len(), 2);
}
#[test]
fn s3_fifo_empties_rather_than_spinning() {
let mut s = S3Fifo::new(64, 4);
for slot in 0..3 {
s.insert(slot, finger(slot));
s.touch(slot);
}
let mut out = 0;
while s.demote(finger).is_some() {
out += 1;
assert!(out <= 3, "demote is not making progress");
}
assert_eq!(out, 3);
assert!(s.is_empty());
}
#[test]
fn a_second_read_gets_in_and_a_first_does_not() {
let mut d = Doorkeeper::new(1024);
assert!(!d.admit(finger(7)));
assert!(d.admit(finger(7)));
}
#[test]
fn the_doorkeeper_clears_itself_before_it_saturates() {
let mut d = Doorkeeper::new(64);
for slot in 0..64 {
assert!(!d.admit(finger(slot)));
}
assert!(!d.admit(finger(0)));
}
#[test]
fn the_doorkeeper_is_a_few_kilobytes_and_not_a_few_megabytes() {
let d = Doorkeeper::new(1_000_000);
assert!(d.memory_bytes() < 1_100_000, "{} bytes", d.memory_bytes());
}
#[test]
fn both_policies_survive_a_random_workload() {
let slots = 200u32;
let mut sieve = Sieve::new(slots as usize);
let mut s3 = S3Fifo::new(slots as usize, 40);
let mut resident_sieve = vec![false; slots as usize];
let mut resident_s3 = vec![false; slots as usize];
let mut x = 0x1234_5678_9abc_def0u64;
for _ in 0..20_000 {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
let slot = (x % u64::from(slots)) as u32;
match x % 4 {
0 => {
sieve.insert(slot);
resident_sieve[slot as usize] = true;
s3.insert(slot, finger(slot));
resident_s3[slot as usize] = true;
}
1 => {
sieve.touch(slot);
s3.touch(slot);
}
2 => {
if let Some(out) = sieve.demote() {
assert!(resident_sieve[out as usize], "sieve gave back a ghost");
resident_sieve[out as usize] = false;
}
if let Some(out) = s3.demote(finger) {
assert!(resident_s3[out as usize], "s3 gave back a ghost");
resident_s3[out as usize] = false;
}
}
_ => {
sieve.remove(slot);
resident_sieve[slot as usize] = false;
s3.remove(slot);
resident_s3[slot as usize] = false;
}
}
assert_eq!(sieve.len(), resident_sieve.iter().filter(|r| **r).count());
assert_eq!(s3.len(), resident_s3.iter().filter(|r| **r).count());
}
}
}