slab alloc: use linked list
This commit is contained in:
parent
202cacd764
commit
ef7f924143
|
|
@ -6,7 +6,10 @@ use core::{
|
||||||
|
|
||||||
use liballoc::alloc::Allocator;
|
use liballoc::alloc::Allocator;
|
||||||
|
|
||||||
use crate::mem;
|
use crate::{
|
||||||
|
collections::linked_list::{LinkedList, LinkedListNode},
|
||||||
|
mem,
|
||||||
|
};
|
||||||
|
|
||||||
const PAGE_SIZE: usize = 4096;
|
const PAGE_SIZE: usize = 4096;
|
||||||
|
|
||||||
|
|
@ -14,9 +17,9 @@ pub struct Slab<A: Allocator + Clone> {
|
||||||
/// Size and alignment of each element in the slab.
|
/// Size and alignment of each element in the slab.
|
||||||
element_size: usize,
|
element_size: usize,
|
||||||
/// Pointer to the first chunk in the slab.
|
/// Pointer to the first chunk in the slab.
|
||||||
head: Option<NonNull<SlabChunk<A>>>,
|
chunks: LinkedList<SlabChunk<A>>,
|
||||||
/// Pointer to the first chunk in the slab that is full.
|
/// Pointer to the first chunk in the slab that is full.
|
||||||
full_head: Option<NonNull<SlabChunk<A>>>,
|
full_chunks: LinkedList<SlabChunk<A>>,
|
||||||
alloc: A,
|
alloc: A,
|
||||||
_pd: core::marker::PhantomPinned,
|
_pd: core::marker::PhantomPinned,
|
||||||
}
|
}
|
||||||
|
|
@ -25,12 +28,11 @@ impl<A: Allocator + Clone> Debug for Slab<A> {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
f.debug_struct("Slab")
|
f.debug_struct("Slab")
|
||||||
.field("element_size", &self.element_size)
|
.field("element_size", &self.element_size)
|
||||||
.field("head", &self.head)
|
.finish_non_exhaustive()
|
||||||
.field("full_head", &self.full_head)
|
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct SlabChunk<A: Allocator + Clone> {
|
struct SlabChunk<A: Allocator + Clone> {
|
||||||
/// Pointer to the next chunk in the slab.
|
/// Pointer to the next chunk in the slab.
|
||||||
next: Option<NonNull<SlabChunk<A>>>,
|
next: Option<NonNull<SlabChunk<A>>>,
|
||||||
|
|
@ -44,6 +46,16 @@ struct SlabChunk<A: Allocator + Clone> {
|
||||||
count: Cell<usize>,
|
count: Cell<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl<A: Allocator + Clone> LinkedListNode for SlabChunk<A> {
|
||||||
|
fn next(this: NonNull<Self>) -> Option<NonNull<Self>> {
|
||||||
|
unsafe { (&raw const (*this.as_ptr()).next).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_next(this: NonNull<Self>, next: Option<NonNull<Self>>) {
|
||||||
|
unsafe { (&raw mut (*this.as_ptr()).next).write(next) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ChunkSlot(Option<NonNull<Self>>);
|
struct ChunkSlot(Option<NonNull<Self>>);
|
||||||
|
|
||||||
enum SlotResult {
|
enum SlotResult {
|
||||||
|
|
@ -109,8 +121,8 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
|
|
||||||
let slab = Self {
|
let slab = Self {
|
||||||
element_size,
|
element_size,
|
||||||
head: None,
|
chunks: LinkedList::new(),
|
||||||
full_head: None,
|
full_chunks: LinkedList::new(),
|
||||||
alloc: alloc.clone(),
|
alloc: alloc.clone(),
|
||||||
_pd: core::marker::PhantomPinned,
|
_pd: core::marker::PhantomPinned,
|
||||||
};
|
};
|
||||||
|
|
@ -155,16 +167,17 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_chunk(self: Pin<&mut Self>) -> NonNull<SlabChunk<A>> {
|
fn alloc_chunk(element_size: usize, alloc: &A, slab: NonNull<Self>) -> NonNull<SlabChunk<A>> {
|
||||||
// we want to limit chunks to 1 page unless the element size is so
|
// we want to limit chunks to 1 page unless the element size is so
|
||||||
// large that we can fit fewer than 3 elements in a page.
|
// large that we can fit fewer than 3 elements in a page.
|
||||||
let (count, layout) = Self::count_and_layout(self.element_size);
|
let (count, layout) = Self::count_and_layout(element_size);
|
||||||
|
|
||||||
let Some(bytes) = self.alloc.allocate(layout).ok() else {
|
let Some(bytes) = alloc.allocate(layout).ok() else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunk = bytes.as_non_null_ptr().cast::<SlabChunk<A>>();
|
let chunk = bytes.as_non_null_ptr().cast::<SlabChunk<A>>();
|
||||||
|
let _ = chunk.as_ptr().expose_provenance();
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
std::eprintln!("Slab::alloc_chunk(chunk: {chunk:#?}, layout: {layout:?})");
|
std::eprintln!("Slab::alloc_chunk(chunk: {chunk:#?}, layout: {layout:?})");
|
||||||
|
|
@ -172,22 +185,22 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let first_slot = chunk
|
let first_slot = chunk
|
||||||
.as_ptr()
|
.as_ptr()
|
||||||
.byte_add(Self::first_slot_offset(self.element_size))
|
.byte_add(Self::first_slot_offset(element_size))
|
||||||
.cast::<ChunkSlot>();
|
.cast::<ChunkSlot>();
|
||||||
|
|
||||||
for i in 0..(count - 1) {
|
for i in 0..(count - 1) {
|
||||||
let chunk = first_slot.byte_add(i * self.element_size);
|
let chunk = first_slot.byte_add(i * element_size);
|
||||||
let next = first_slot.byte_add((i + 1) * self.element_size);
|
let next = first_slot.byte_add((i + 1) * element_size);
|
||||||
chunk.write(ChunkSlot(Some(NonNull::new_unchecked(next))));
|
chunk.write(ChunkSlot(Some(NonNull::new_unchecked(next))));
|
||||||
}
|
}
|
||||||
first_slot
|
first_slot
|
||||||
.byte_add((count - 1) * self.element_size)
|
.byte_add((count - 1) * element_size)
|
||||||
.write(ChunkSlot(None));
|
.write(ChunkSlot(None));
|
||||||
|
|
||||||
chunk.write(SlabChunk {
|
chunk.write(SlabChunk {
|
||||||
next: self.head,
|
next: None,
|
||||||
// SAFETY: we only access `slab` via `SlabChunk::slab_pinned_mut`
|
// SAFETY: we only access `slab` via `SlabChunk::slab_pinned_mut`
|
||||||
slab: NonNull::from(Pin::into_inner_unchecked(self)),
|
slab,
|
||||||
free: Cell::new(Some(NonNull::new_unchecked(first_slot))),
|
free: Cell::new(Some(NonNull::new_unchecked(first_slot))),
|
||||||
count: Cell::new(0),
|
count: Cell::new(0),
|
||||||
});
|
});
|
||||||
|
|
@ -196,36 +209,31 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
chunk
|
chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cold]
|
|
||||||
fn alloc_chunk_cold(self: Pin<&mut Self>) -> NonNull<SlabChunk<A>> {
|
|
||||||
self.alloc_chunk()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_slot(mut self: Pin<&mut Self>) -> NonNull<[u8]> {
|
fn alloc_slot(mut self: Pin<&mut Self>) -> NonNull<[u8]> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
std::eprintln!("Slab::alloc_slot({self:?})");
|
std::eprintln!("Slab::alloc_slot({self:?})");
|
||||||
|
|
||||||
let chunk = match self.head {
|
let slab: NonNull<Self> = unsafe { self.as_mut().get_unchecked_mut().into() };
|
||||||
Some(chunk) => chunk,
|
let alloc = self.alloc.clone();
|
||||||
None => {
|
let element_size = self.element_size;
|
||||||
let chunk = self.as_mut().alloc_chunk_cold();
|
|
||||||
unsafe {
|
let mut chunk = unsafe {
|
||||||
self.as_mut().get_unchecked_mut().head = Some(chunk);
|
self.as_mut()
|
||||||
}
|
.get_unchecked_mut()
|
||||||
chunk
|
.chunks
|
||||||
}
|
.get_or_insert_front_with(move || Self::alloc_chunk(element_size, &alloc, slab))
|
||||||
};
|
};
|
||||||
|
|
||||||
let chunk = unsafe { chunk.as_ptr().as_mut_unchecked() };
|
let chunk_ref = chunk.get_mut();
|
||||||
|
|
||||||
let ptr = match chunk.pop_free_slot() {
|
let ptr = match chunk_ref.pop_free_slot() {
|
||||||
SlotResult::Some(non_null) => non_null,
|
SlotResult::Some(non_null) => non_null,
|
||||||
SlotResult::Last(non_null) => {
|
SlotResult::Last(non_null) => {
|
||||||
|
let chunk = chunk.remove();
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut_ref = self.as_mut().get_unchecked_mut();
|
self.as_mut()
|
||||||
mut_ref.head = chunk.next.take();
|
.map_unchecked_mut(|slab| &mut slab.full_chunks)
|
||||||
chunk.next = mut_ref.full_head;
|
.push_front(chunk);
|
||||||
mut_ref.full_head = Some(chunk.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
non_null
|
non_null
|
||||||
|
|
@ -241,11 +249,14 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
fn free_slot(element_size: usize, slot: NonNull<u8>) {
|
fn free_slot(element_size: usize, slot: NonNull<u8>) {
|
||||||
let (_, layout) = Self::count_and_layout(element_size);
|
let (_, layout) = Self::count_and_layout(element_size);
|
||||||
|
|
||||||
let chunk_ptr = slot
|
let addr =
|
||||||
.map_addr(|addr| unsafe {
|
unsafe { NonZero::new_unchecked(mem::align_down(slot.addr().get(), layout.align())) };
|
||||||
NonZero::new_unchecked(mem::align_down(addr.get(), layout.align()))
|
|
||||||
})
|
// grab provenance exposed in alloc_chunk, since `slot` may be noalias restricted to its layout.
|
||||||
.cast::<SlabChunk<A>>();
|
|
||||||
|
// let chunk_ptr: NonNull<SlabChunk<A>> = NonNull::with_exposed_provenance(addr);
|
||||||
|
|
||||||
|
let chunk_ptr: NonNull<SlabChunk<A>> = slot.with_addr(addr).cast();
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
std::eprintln!("Slab::free_slot(chunk: {chunk_ptr:#?})");
|
std::eprintln!("Slab::free_slot(chunk: {chunk_ptr:#?})");
|
||||||
|
|
@ -258,47 +269,35 @@ impl<A: Allocator + Clone> Slab<A> {
|
||||||
FreeSlotResult::Empty => {
|
FreeSlotResult::Empty => {
|
||||||
// chunk is empty:
|
// chunk is empty:
|
||||||
// first unlink the chunk from the slab..
|
// first unlink the chunk from the slab..
|
||||||
if slab.head == Some(chunk.into()) {
|
if let Some(chunk) =
|
||||||
unsafe { slab.as_mut().get_unchecked_mut().head = chunk.next };
|
unsafe { slab.as_mut().map_unchecked_mut(|slab| &mut slab.chunks) }
|
||||||
} else {
|
.remove_if(|c| c == chunk_ptr)
|
||||||
let mut head = slab.head.expect("chunk is linked, so head exists");
|
.next()
|
||||||
while let Some(next) = unsafe { (&raw const (*head.as_ptr()).next).read() } {
|
{
|
||||||
if next == chunk.into() {
|
// ..then free it.
|
||||||
unsafe { (&raw mut (*head.as_ptr()).next).write(chunk.next) };
|
#[cfg(test)]
|
||||||
break;
|
std::eprintln!("free_slot::drop({chunk:?}, layout: {layout:?})");
|
||||||
}
|
|
||||||
head = next;
|
unsafe { slab.alloc.deallocate(chunk.cast(), layout) };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..then free it.
|
|
||||||
#[cfg(test)]
|
|
||||||
std::eprintln!("free_slot::drop({chunk_ptr:?}, layout: {layout:?})");
|
|
||||||
|
|
||||||
unsafe { slab.alloc.deallocate(chunk_ptr.cast(), layout) };
|
|
||||||
}
|
}
|
||||||
FreeSlotResult::WasFull => {
|
FreeSlotResult::WasFull => {
|
||||||
// chunk was full:
|
// chunk was full:
|
||||||
|
|
||||||
// unlink it from the full list
|
// unlink it from the full list
|
||||||
if slab.full_head == Some(chunk.into()) {
|
if let Some(chunk) = unsafe {
|
||||||
unsafe { slab.as_mut().get_unchecked_mut().full_head = chunk.next };
|
slab.as_mut()
|
||||||
} else {
|
.map_unchecked_mut(|slab| &mut slab.full_chunks)
|
||||||
let mut head = slab.full_head.expect("chunk is linked, so head exists");
|
|
||||||
while let Some(next) = unsafe { (&raw const (*head.as_ptr()).next).read() } {
|
|
||||||
if next == chunk.into() {
|
|
||||||
unsafe { (&raw mut (*head.as_ptr()).next).write(chunk.next) };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
head = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// link it to the head of the slab
|
|
||||||
chunk.next = slab.head;
|
|
||||||
unsafe {
|
|
||||||
slab.as_mut().get_unchecked_mut().head = Some(chunk.into());
|
|
||||||
}
|
}
|
||||||
|
.remove_if(|c| c == chunk_ptr)
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
slab.as_mut()
|
||||||
|
.map_unchecked_mut(|slab| &mut slab.chunks)
|
||||||
|
.push_front(chunk)
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
FreeSlotResult::NotEmpty => {
|
FreeSlotResult::NotEmpty => {
|
||||||
// chunk is not empty, and was not full, so nothing to do
|
// chunk is not empty, and was not full, so nothing to do
|
||||||
|
|
@ -311,26 +310,21 @@ impl<A: Allocator + Clone> Drop for Slab<A> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let (_, layout) = Self::count_and_layout(self.element_size);
|
let (_, layout) = Self::count_and_layout(self.element_size);
|
||||||
|
|
||||||
let mut chunk = self.head.take();
|
for chunk in self.chunks.iter() {
|
||||||
while let Some(mut chunk_ptr) = chunk {
|
|
||||||
let chunk_ref = unsafe { chunk_ptr.as_mut() };
|
|
||||||
chunk = chunk_ref.next.take();
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
std::eprintln!("Slab::drop({chunk_ptr:?}, layout: {layout:?})");
|
std::eprintln!("Slab::drop({chunk:?}, layout: {layout:?})");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.alloc.deallocate(chunk_ptr.cast(), layout);
|
self.alloc.deallocate(chunk.cast(), layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chunk = self.full_head.take();
|
for chunk in self.full_chunks.iter() {
|
||||||
while let Some(mut chunk_ptr) = chunk {
|
#[cfg(test)]
|
||||||
let chunk_ref = unsafe { chunk_ptr.as_mut() };
|
std::eprintln!("Slab::drop({chunk:?}, layout: {layout:?})");
|
||||||
chunk = chunk_ref.next.take();
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.alloc.deallocate(chunk_ptr.cast(), layout);
|
self.alloc.deallocate(chunk.cast(), layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +432,7 @@ impl<A: Allocator + Clone> SlabAllocator<A> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use core::{mem::forget, pin};
|
use core::pin;
|
||||||
use std::{prelude::rust_2024::*, sync::Mutex};
|
use std::{prelude::rust_2024::*, sync::Mutex};
|
||||||
|
|
||||||
struct SlabAllocatorWrapper<'a, A: Allocator + Clone>(Mutex<Pin<&'a mut SlabAllocator<A>>>);
|
struct SlabAllocatorWrapper<'a, A: Allocator + Clone>(Mutex<Pin<&'a mut SlabAllocator<A>>>);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue