use std::mem;
pub trait Bytes {
fn memory_bytes(&self) -> usize;
}
const NONE: u32 = u32::MAX;
pub const MAX_SLOTS: usize = NONE as usize;
#[derive(Debug)]
enum Slot<T> {
Filled(T),
Free(u32),
}
#[derive(Debug)]
pub struct Slab<T> {
slots: Vec<Slot<T>>,
free: u32,
len: usize,
clean: usize,
soiled: Vec<u32>,
mark: Vec<u64>,
track: bool,
}
impl<T: Bytes> Slab<T> {
#[must_use]
pub fn new() -> Slab<T> {
Slab {
slots: Vec::new(),
free: NONE,
len: 0,
clean: 0,
soiled: Vec::new(),
mark: Vec::new(),
track: false,
}
}
#[must_use]
pub fn with_capacity(n: usize) -> Slab<T> {
Slab {
slots: Vec::with_capacity(n),
..Slab::new()
}
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn insert(&mut self, value: T) -> u32 {
if self.free != NONE {
let at = self.free as usize;
let Slot::Free(next) = self.slots[at] else {
unreachable!("the free list only ever points at free slots");
};
self.free = next;
self.soil(at as u32);
self.slots[at] = Slot::Filled(value);
self.len += 1;
return at as u32;
}
assert!(self.slots.len() < MAX_SLOTS, "slab is full");
let at = self.slots.len() as u32;
self.soil(at);
self.slots.push(Slot::Filled(value));
self.len += 1;
at
}
#[inline]
pub fn get(&self, at: u32) -> Option<&T> {
match self.slots.get(at as usize) {
Some(Slot::Filled(v)) => Some(v),
_ => None,
}
}
#[inline]
pub fn get_mut(&mut self, at: u32) -> Option<&mut T> {
if self.track && (at as usize) < self.slots.len() {
self.soil(at);
}
match self.slots.get_mut(at as usize) {
Some(Slot::Filled(v)) => Some(v),
_ => None,
}
}
pub fn remove(&mut self, at: u32) -> Option<T> {
if self.track && (at as usize) < self.slots.len() {
self.soil(at);
}
match self.slots.get_mut(at as usize) {
Some(slot @ Slot::Filled(_)) => {
let taken = mem::replace(slot, Slot::Free(self.free));
self.free = at;
self.len -= 1;
match taken {
Slot::Filled(v) => Some(v),
Slot::Free(_) => unreachable!("just matched on filled"),
}
}
_ => None,
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.slots.iter().filter_map(|s| match s {
Slot::Filled(v) => Some(v),
Slot::Free(_) => None,
})
}
pub fn clear(&mut self) {
self.slots = Vec::new();
self.free = NONE;
self.len = 0;
self.clean = 0;
self.soiled = Vec::new();
self.mark = Vec::new();
}
pub fn slot_bytes(&self) -> usize {
self.slots.capacity() * mem::size_of::<Slot<T>>()
}
#[deprecated(since = "0.3.8", note = "renamed to slot_bytes")]
pub fn memory_bytes(&self) -> usize {
self.slot_bytes()
}
pub fn value_bytes(&self) -> usize {
self.iter().map(T::memory_bytes).sum()
}
pub fn settled_bytes(&mut self) -> usize {
if !self.track {
return self.value_bytes();
}
while let Some(at) = self.soiled.pop() {
self.mark[at as usize / 64] &= !(1u64 << (at % 64));
if let Some(Slot::Filled(v)) = self.slots.get(at as usize) {
self.clean += v.memory_bytes();
}
}
self.clean
}
pub fn track_bytes(&mut self, on: bool) {
if on == self.track {
return;
}
self.track = on;
self.soiled = Vec::new();
self.mark = Vec::new();
self.clean = if on { self.value_bytes() } else { 0 };
}
#[inline]
fn soil(&mut self, at: u32) {
if !self.track {
return;
}
let word = at as usize / 64;
let bit = 1u64 << (at % 64);
if word >= self.mark.len() {
self.mark.resize(word + 1, 0);
}
if self.mark[word] & bit != 0 {
return;
}
self.mark[word] |= bit;
self.soiled.push(at);
if let Some(Slot::Filled(v)) = self.slots.get(at as usize) {
self.clean -= v.memory_bytes();
}
}
}
impl<T: Bytes> Default for Slab<T> {
fn default() -> Slab<T> {
Slab::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
impl Bytes for String {
fn memory_bytes(&self) -> usize {
self.len()
}
}
impl Bytes for Vec<u8> {
fn memory_bytes(&self) -> usize {
self.len()
}
}
impl Bytes for i32 {
fn memory_bytes(&self) -> usize {
0
}
}
impl Bytes for u8 {
fn memory_bytes(&self) -> usize {
0
}
}
#[test]
fn a_new_slab_holds_nothing_and_has_allocated_nothing() {
let s: Slab<String> = Slab::new();
assert_eq!(s.len(), 0);
assert!(s.is_empty());
assert_eq!(s.get(0), None);
assert_eq!(s.slot_bytes(), 0);
}
#[test]
fn what_goes_in_comes_back_out_at_the_number_it_was_given() {
let mut s = Slab::new();
let a = s.insert("a".to_string());
let b = s.insert("b".to_string());
let c = s.insert("c".to_string());
assert_eq!((a, b, c), (0, 1, 2), "the first three go at the end");
assert_eq!(s.get(a).map(String::as_str), Some("a"));
assert_eq!(s.get(b).map(String::as_str), Some("b"));
assert_eq!(s.get(c).map(String::as_str), Some("c"));
assert_eq!(s.len(), 3);
}
#[test]
fn a_value_can_be_changed_where_it_lies() {
let mut s = Slab::new();
let a = s.insert(vec![1u8]);
s.get_mut(a).expect("filled").push(2);
assert_eq!(s.get(a), Some(&vec![1, 2]));
assert_eq!(s.get_mut(9), None, "past the end");
}
#[test]
fn removing_hands_the_value_back_and_the_others_keep_their_numbers() {
let mut s = Slab::new();
let a = s.insert("a".to_string());
let b = s.insert("b".to_string());
let c = s.insert("c".to_string());
assert_eq!(s.remove(b), Some("b".to_string()));
assert_eq!(s.len(), 2);
assert_eq!(s.get(b), None);
assert_eq!(
s.get(a).map(String::as_str),
Some("a"),
"a did not move when b left"
);
assert_eq!(s.get(c).map(String::as_str), Some("c"));
}
#[test]
fn a_freed_slot_is_the_next_one_used() {
let mut s = Slab::new();
s.insert(0);
let b = s.insert(1);
s.insert(2);
s.remove(b);
let next = s.insert(9);
assert_eq!(next, b, "the hole was filled rather than the vector grown");
assert_eq!(s.len(), 3);
assert_eq!(s.get(b), Some(&9));
}
#[test]
fn the_free_list_gives_the_holes_back_in_reverse() {
let mut s = Slab::new();
let n: Vec<u32> = (0..5).map(|i| s.insert(i)).collect();
for i in [1, 3, 4] {
s.remove(n[i]);
}
assert_eq!(s.len(), 2);
assert_eq!(s.insert(50), 4);
assert_eq!(s.insert(51), 3);
assert_eq!(s.insert(52), 1);
assert_eq!(s.len(), 5);
assert_eq!(s.insert(53), 5);
assert_eq!(s.get(0), Some(&0), "the untouched ones are untouched");
assert_eq!(s.get(2), Some(&2));
}
#[test]
fn freeing_twice_is_inert_rather_than_a_loop_in_the_list() {
let mut s = Slab::new();
let a = s.insert("a".to_string());
let b = s.insert("b".to_string());
assert_eq!(s.remove(a), Some("a".to_string()));
assert_eq!(s.remove(a), None, "already free");
assert_eq!(s.remove(a), None, "still already free");
assert_eq!(s.len(), 1);
let x = s.insert("x".to_string());
let y = s.insert("y".to_string());
let z = s.insert("z".to_string());
assert_eq!(x, a, "the one real hole came back");
assert_ne!(y, x);
assert_ne!(z, x);
assert_ne!(z, y);
assert_eq!(s.len(), 4);
assert_eq!(
s.get(b).map(String::as_str),
Some("b"),
"b was never touched"
);
}
#[test]
fn removing_something_that_was_never_there_answers_nothing() {
let mut s: Slab<u8> = Slab::new();
assert_eq!(s.remove(0), None);
assert_eq!(s.remove(7), None);
assert_eq!(s.len(), 0);
assert_eq!(s.insert(1), 0, "and it did not corrupt the free list");
}
#[test]
fn iterating_sees_the_values_and_not_the_holes() {
let mut s = Slab::new();
let n: Vec<u32> = (0..6).map(|i| s.insert(i * 10)).collect();
s.remove(n[0]);
s.remove(n[3]);
s.remove(n[5]);
let mut got: Vec<i32> = s.iter().copied().collect();
got.sort_unstable();
assert_eq!(got, [10, 20, 40]);
assert_eq!(got.len(), s.len());
}
#[test]
fn clearing_hands_the_memory_back_and_starts_the_numbers_again() {
let mut s = Slab::with_capacity(64);
for i in 0..64 {
s.insert(i);
}
assert!(s.slot_bytes() >= 64 * mem::size_of::<Slot<i32>>());
s.clear();
assert_eq!(s.len(), 0);
assert!(s.is_empty());
assert_eq!(s.slot_bytes(), 0, "the vector went, not just the values");
assert_eq!(s.get(0), None);
assert_eq!(s.insert(1), 0, "numbering starts over");
}
#[test]
fn the_running_total_says_what_the_walk_says_whatever_was_done_to_it() {
let mut s: Slab<String> = Slab::new();
s.track_bytes(true);
let mut live: Vec<u32> = Vec::new();
let mut n = 0usize;
for step in 0..500 {
match step % 5 {
0 | 1 => {
n += 1;
live.push(s.insert("x".repeat(n % 17)));
}
2 | 3 => {
if let Some(&at) = live.get(step % live.len().max(1)) {
s.get_mut(at).expect("filled").push('y');
}
}
_ => {
if !live.is_empty() {
let at = live.swap_remove(step % live.len());
s.remove(at);
}
}
}
assert_eq!(
s.settled_bytes(),
s.value_bytes(),
"after step {step}, which was a {}",
step % 5
);
}
assert!(n > 0 && !live.is_empty(), "the run did something");
}
#[test]
fn a_slot_written_over_and_over_is_only_asked_once_before_a_reading() {
let mut s: Slab<String> = Slab::new();
s.track_bytes(true);
let a = s.insert(String::new());
let b = s.insert("bb".to_string());
assert_eq!(s.settled_bytes(), 2);
for _ in 0..64 {
s.get_mut(a).expect("filled").push('a');
}
assert_eq!(s.soiled.len(), 1, "one slot written down, not sixty four");
assert_eq!(s.settled_bytes(), 66);
assert_eq!(s.soiled.len(), 0, "and the list is empty again");
assert_eq!(s.get(b).map(String::as_str), Some("bb"));
}
#[test]
fn nothing_is_counted_until_the_total_is_switched_on() {
let mut s: Slab<String> = Slab::new();
s.insert("abc".to_string());
s.insert("de".to_string());
assert_eq!(s.settled_bytes(), 5, "the walk, because nothing is tracked");
assert_eq!(s.clean, 0, "and it did not start a total behind our back");
s.track_bytes(true);
assert_eq!(s.clean, 5, "the walk it starts from");
s.track_bytes(true);
assert_eq!(s.clean, 5);
s.insert("fghi".to_string());
assert_eq!(s.settled_bytes(), 9);
s.track_bytes(false);
assert_eq!(s.clean, 0, "and it gave the bookkeeping back");
assert_eq!(s.settled_bytes(), 9, "walking again for the same answer");
}
#[test]
fn clearing_takes_the_total_with_it() {
let mut s: Slab<String> = Slab::new();
s.track_bytes(true);
for i in 0..10 {
s.insert("z".repeat(i));
}
assert_eq!(s.settled_bytes(), 45);
s.clear();
assert_eq!(s.settled_bytes(), 0);
assert_eq!(s.slot_bytes(), 0);
s.insert("new".to_string());
assert_eq!(s.settled_bytes(), 3, "and it counts again from there");
}
#[test]
fn a_reused_slot_is_counted_as_what_is_in_it_now() {
let mut s: Slab<String> = Slab::new();
s.track_bytes(true);
let a = s.insert("aaaaa".to_string());
assert_eq!(s.settled_bytes(), 5);
s.remove(a);
let b = s.insert("bb".to_string());
assert_eq!(b, a, "the same slot came back");
assert_eq!(s.settled_bytes(), 2);
assert_eq!(s.value_bytes(), 2);
}
#[test]
fn a_churning_workload_does_not_grow_the_vector() {
let mut s = Slab::with_capacity(4);
let before = s.slot_bytes();
for i in 0..10_000 {
let at = s.insert(i);
assert_eq!(at, 0, "the same slot every time");
assert_eq!(s.remove(at), Some(i));
}
assert_eq!(s.len(), 0);
assert_eq!(s.slot_bytes(), before);
}
}