add smaller slabs

This commit is contained in:
soruh 2023-07-25 17:10:32 +02:00
parent 2f3c46136b
commit 42eda592ff
2 changed files with 290 additions and 81 deletions

View File

@ -2,10 +2,11 @@ use std::mem::size_of;
use zerocopy::{AsBytes, FromBytes, Unaligned}; use zerocopy::{AsBytes, FromBytes, Unaligned};
use crate::{Db, FilePointer, FileRange, Header, PAGE_SIZE, U32}; use crate::{Db, FilePointer, FileRange, PagePointer, PAGE_SIZE, U16, U32};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum SlabKind { enum SlabKind {
SplitFreeList, SingleBytes,
RelativeFreeList, RelativeFreeList,
AbsoluteFreeList, AbsoluteFreeList,
} }
@ -13,8 +14,8 @@ enum SlabKind {
impl SlabKind { impl SlabKind {
fn for_size(size: u32) -> Self { fn for_size(size: u32) -> Self {
if size == 1 { if size == 1 {
Self::SplitFreeList Self::SingleBytes
} else if size < 8 { } else if size < size_of::<FilePointer>() as u32 {
Self::RelativeFreeList Self::RelativeFreeList
} else if (size as u64) <= PAGE_SIZE { } else if (size as u64) <= PAGE_SIZE {
Self::AbsoluteFreeList Self::AbsoluteFreeList
@ -60,6 +61,28 @@ pub struct SlabListHeader {
#[repr(transparent)] #[repr(transparent)]
pub struct SlabListPointer(pub FilePointer); pub struct SlabListPointer(pub FilePointer);
pub struct SlabListIterator<'db> {
position: u32,
db: &'db Db,
ptr: SlabListPointer,
}
impl<'db> Iterator for SlabListIterator<'db> {
type Item = SlabPointer;
fn next(&mut self) -> Option<Self::Item> {
if let Some(res) = self.ptr.get(self.db, self.position) {
self.position += 1;
Some(res)
} else if let Some(next) = self.ptr.next(self.db) {
self.ptr = next;
self.next()
} else {
None
}
}
}
impl SlabListHeader { impl SlabListHeader {
pub fn capacity(&self) -> u32 { pub fn capacity(&self) -> u32 {
(self.size.get() - size_of::<SlabListHeader>() as u32) / size_of::<Slab>() as u32 (self.size.get() - size_of::<SlabListHeader>() as u32) / size_of::<Slab>() as u32
@ -67,49 +90,68 @@ impl SlabListHeader {
} }
impl SlabListPointer { impl SlabListPointer {
pub fn set_next(&self, db: &mut Db, next: SlabListPointer) { pub fn next(self, db: &Db) -> Option<SlabListPointer> {
db.write(self.0, next); let ptr: SlabListPointer = self.read_header(db).next;
(!ptr.0.is_null()).then_some(ptr)
} }
pub fn set_len(&self, db: &mut Db, len: u32) { fn read_header(self, db: &Db) -> SlabListHeader {
db.write(self.0 + size_of::<SlabListPointer>() as u64, U32::from(len)); unsafe { db.read(self.0) }
} }
pub fn init(&self, db: &mut Db, size: u32) { fn modify_header(self, db: &mut Db) -> &mut SlabListHeader {
db.write( unsafe { db.modify(self.0) }
self.0,
SlabListHeader {
next: SlabListPointer(FilePointer::null()),
size: size.into(),
len: 0.into(),
},
);
} }
pub fn ptr(&self, db: &Db, i: u32) -> FilePointer { pub fn set_next(self, db: &mut Db, next: SlabListPointer) {
let this: SlabListHeader = db.read(self.0); self.modify_header(db).next = next;
assert!(i < this.len.get());
self.0 + size_of::<SlabListHeader>() as u64 + i as u64 * size_of::<Slab>() as u64
} }
pub fn write(&self, db: &mut Db, i: u32, value: Slab) { pub fn set_len(self, db: &mut Db, len: u32) {
let ptr = self.ptr(db, i); self.modify_header(db).len = U32::from(len);
db.write(ptr, value);
} }
pub fn get(&self, db: &Db, i: u32) -> SlabPointer { pub fn init(self, db: &mut Db, size: u32) {
let ptr = self.ptr(db, i); *self.modify_header(db) = SlabListHeader {
SlabPointer(ptr) next: SlabListPointer(FilePointer::null()),
size: size.into(),
len: 0.into(),
};
} }
pub fn add(&self, db: &mut Db, slab_size: u32) -> SlabPointer { pub fn ptr(self, db: &Db, i: u32) -> Option<FilePointer> {
let this: SlabListHeader = db.read(self.0); let this = self.read_header(db);
(i < this.len.get()).then(|| {
self.0 + size_of::<SlabListHeader>() as u64 + i as u64 * size_of::<Slab>() as u64
})
}
pub fn write(self, db: &mut Db, i: u32, value: Slab) {
let ptr = self.ptr(db, i).unwrap();
unsafe { db.write(ptr, value) };
}
pub fn get(self, db: &Db, i: u32) -> Option<SlabPointer> {
self.ptr(db, i).map(SlabPointer)
}
pub fn iter(self, db: &Db) -> SlabListIterator {
SlabListIterator {
position: 0,
db,
ptr: self,
}
}
pub fn add(self, db: &mut Db, slab_size: u32) -> SlabPointer {
let this = self.read_header(db);
let capacity = this.capacity(); let capacity = this.capacity();
let SlabListHeader { mut next, len, .. } = this; let SlabListHeader { mut next, len, .. } = this;
if len.get() >= capacity { if len.get() >= capacity {
if next.0 == FilePointer::null() { if next.0.is_null() {
next = SlabListPointer(db.add_pages(1)); next = SlabListPointer(db.add_pages(1).start());
next.init(db, PAGE_SIZE as u32); next.init(db, PAGE_SIZE as u32);
self.set_next(db, next); self.set_next(db, next);
} }
@ -128,7 +170,7 @@ impl SlabListPointer {
}, },
); );
SlabPointer(self.ptr(db, len)) SlabPointer(self.ptr(db, len).unwrap())
} }
} }
@ -139,51 +181,169 @@ pub struct Slab {
size: U32, size: U32,
} }
#[derive(Clone, Copy, FromBytes, AsBytes, Unaligned)]
#[repr(C)]
struct RelativeFreeListHeader {
next_page: PagePointer,
first: U16,
}
impl RelativeFreeListHeader {
const DATA_SIZE: u32 = PAGE_SIZE as u32 - size_of::<Self>() as u32;
fn capacity(size: u32) -> u32 {
debug_assert_eq!(SlabKind::for_size(size), SlabKind::RelativeFreeList);
Self::DATA_SIZE / size
}
}
#[derive(Clone, Copy, FromBytes, AsBytes, Unaligned)] #[derive(Clone, Copy, FromBytes, AsBytes, Unaligned)]
#[repr(transparent)] #[repr(transparent)]
pub struct SlabPointer(FilePointer); pub struct SlabPointer(FilePointer);
impl SlabPointer { impl SlabPointer {
pub fn read(&self, db: &Db) -> Slab { fn read(&self, db: &Db) -> Slab {
db.read(self.0) unsafe { db.read(self.0) }
} }
pub fn get(&self, db: &mut Db) -> FileRange { fn modify<'db>(&self, db: &'db mut Db) -> &'db mut Slab {
let Slab { head, size } = self.read(db); unsafe { db.modify(self.0) }
}
pub fn alloc(&self, db: &mut Db) -> FileRange {
let Slab { mut head, size } = self.read(db);
if head.is_null() {
head = self.allocate_page(db);
}
let size = size.get(); let size = size.get();
match SlabKind::for_size(size) { match SlabKind::for_size(size) {
SlabKind::SplitFreeList => todo!(), SlabKind::SingleBytes => todo!(),
SlabKind::RelativeFreeList => todo!(), SlabKind::RelativeFreeList => {
SlabKind::AbsoluteFreeList => { let (page, offset) = head.page_offset();
let mut next = head; assert_eq!(offset, 0);
if next == FilePointer::null() { let RelativeFreeListHeader { first, .. } = unsafe { db.read(page.start()) };
next = self.allocate_page(db);
// the page should never be full if its in the free list
assert_ne!(first.get(), 0);
let ptr = FilePointer::from_page_offset(page, first.get());
let next: U16 = unsafe { db.read(ptr) };
let header = unsafe { db.modify::<RelativeFreeListHeader>(page.start()) };
header.first = next;
if next.get() == 0 {
// page is full
let next_page = header.next_page;
header.next_page = PagePointer::null();
self.modify(db).head = next_page.start();
} }
let new_next = db.read(next); ptr
}
SlabKind::AbsoluteFreeList => {
let next = unsafe { db.read(head) };
self.set_head(db, next);
head
}
}
.range(size as u64)
}
self.set_next(db, new_next); pub fn free(&self, db: &mut Db, range: FileRange) {
let Slab { head, size } = self.read(db);
next.range(size as u64) assert_eq!(range.len(), size.get() as u64);
let size = size.get();
match SlabKind::for_size(size) {
SlabKind::SingleBytes => todo!(),
SlabKind::RelativeFreeList => {
let (page, offset) = range.start.page_offset();
let RelativeFreeListHeader { first, .. } = unsafe { db.read(page.start()) };
// update next pointer of new element in free list
unsafe { db.write(range.start, first) };
let header = unsafe { db.modify::<RelativeFreeListHeader>(page.start()) };
// point to new element
header.first = offset.into();
if first.get() == 0 {
// page was full
let (head_page, offset) = head.page_offset();
assert_eq!(offset, 0);
header.next_page = head_page;
self.modify(db).head = page.start();
}
}
SlabKind::AbsoluteFreeList => {
unsafe { db.write(range.start, head) };
self.set_head(db, range.start);
} }
} }
} }
pub fn set_next(&self, db: &mut Db, next: FilePointer) { pub fn set_head(&self, db: &mut Db, next: FilePointer) {
db.write(self.0, next); self.modify(db).head = next;
} }
pub fn allocate_page(&self, db: &mut Db) -> FilePointer { pub fn allocate_page(&self, db: &mut Db) -> FilePointer {
let Slab { head, size } = self.read(db); let Slab { head, size } = self.read(db);
println!("allocate_page({size})");
let size = size.get(); let size = size.get();
match SlabKind::for_size(size) { match SlabKind::for_size(size) {
SlabKind::SplitFreeList => todo!(), SlabKind::SingleBytes => todo!(),
SlabKind::RelativeFreeList => todo!(), SlabKind::RelativeFreeList => {
let page = db.add_pages(1);
let (next_page, offset) = head.page_offset();
assert_eq!(offset, 0);
let capacity = RelativeFreeListHeader::capacity(size);
let data_offset = size_of::<Self>() as u16;
unsafe {
db.write(
page.start(),
RelativeFreeListHeader {
next_page,
first: data_offset.into(),
},
)
};
let mut offset = 0;
for i in (0..capacity).rev() {
let next = data_offset + (i * size) as u16;
unsafe {
db.write(FilePointer::from_page_offset(page, next), U16::from(offset))
};
offset = next;
}
self.set_head(db, page.start());
page.start()
}
SlabKind::AbsoluteFreeList => { SlabKind::AbsoluteFreeList => {
let n = PAGE_SIZE / size as u64; let n = PAGE_SIZE / size as u64;
@ -191,12 +351,12 @@ impl SlabPointer {
let mut next = head; let mut next = head;
for i in (0..n).rev() { for i in (0..n).rev() {
let current = page + i * size as u64; let current = page.start() + i * size as u64;
db.write(current, next); unsafe { db.write(current, next) };
next = current; next = current;
} }
self.set_next(db, next); self.set_head(db, next);
next next
} }

View File

@ -12,18 +12,51 @@ const PAGE_SIZE: u64 = 4096;
type U64 = zerocopy::byteorder::U64<LE>; type U64 = zerocopy::byteorder::U64<LE>;
type U32 = zerocopy::byteorder::U32<LE>; type U32 = zerocopy::byteorder::U32<LE>;
type U16 = zerocopy::byteorder::U16<LE>;
#[derive(Clone, Copy, FromBytes, AsBytes, Unaligned, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Copy, FromBytes, AsBytes, Unaligned, Debug, Hash, PartialEq, Eq)]
#[repr(transparent)] #[repr(transparent)]
pub struct FilePointer(U64); pub struct FilePointer(U64);
impl FilePointer { impl FilePointer {
fn page(n: u64) -> Self { fn page(self) -> PagePointer {
Self((n * PAGE_SIZE).into()) PagePointer(u32::try_from(self.0.get() / PAGE_SIZE).unwrap().into())
}
fn page_offset(self) -> (PagePointer, u16) {
(self.page(), (self.0.get() % PAGE_SIZE) as u16)
}
fn from_page_offset(page: PagePointer, offset: u16) -> Self {
debug_assert!(
offset < PAGE_SIZE as u16,
"offset 0x{offset:x} out for page bounds (0..0x{PAGE_SIZE:x})"
);
page.start() + offset as u64
} }
fn null() -> Self { fn null() -> Self {
Self(U64::ZERO) Self(U64::ZERO)
} }
fn is_null(self) -> bool {
self == Self::null()
}
}
#[derive(Clone, Copy, FromBytes, AsBytes, Unaligned, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct PagePointer(U32);
impl PagePointer {
fn start(self) -> FilePointer {
FilePointer((self.0.get() as u64 * PAGE_SIZE).into())
}
fn range(self) -> FileRange {
self.start().range(PAGE_SIZE)
}
fn nth(n: u32) -> Self {
Self(n.into())
}
fn null() -> Self {
Self::nth(0)
}
} }
#[derive(Clone, Copy, FromBytes, AsBytes, Unaligned, Debug, PartialEq, Eq)] #[derive(Clone, Copy, FromBytes, AsBytes, Unaligned, Debug, PartialEq, Eq)]
@ -75,7 +108,7 @@ impl Default for Header {
root: FilePointer::null(), root: FilePointer::null(),
allocator_state: AllocatorState { allocator_state: AllocatorState {
general: FreeList::empty(), general: FreeList::empty(),
slabs: SlabListPointer(FilePointer::page(0) + size_of::<Header>() as u64), slabs: SlabListPointer(FilePointer::null() + size_of::<Header>() as u64),
}, },
} }
} }
@ -203,7 +236,10 @@ impl Db {
self.map.flush().unwrap(); self.map.flush().unwrap();
// update root pointer and immediately flush // update root pointer and immediately flush
self.write(Self::root_ptr(), new_root); unsafe {
self.write(Self::root_ptr(), new_root);
}
self.map self.map
.flush_range(Self::root_ptr().0.get() as usize, size_of::<FilePointer>()) .flush_range(Self::root_ptr().0.get() as usize, size_of::<FilePointer>())
.unwrap(); .unwrap();
@ -215,41 +251,41 @@ impl Db {
})) }))
} }
fn read<T: FromBytes>(&self, at: FilePointer) -> T { unsafe fn read<T: FromBytes>(&self, at: FilePointer) -> T {
self.read_range(at.range(size_of::<T>() as u64)) self.read_range(at.range(size_of::<T>() as u64))
} }
fn read_range<T: FromBytes>(&self, range: FileRange) -> T { unsafe fn read_range<T: FromBytes>(&self, range: FileRange) -> T {
LayoutVerified::<_, T>::new(&self.map[range.as_range()]) LayoutVerified::<_, T>::new(&self.map[range.as_range()])
.unwrap() .unwrap()
.read() .read()
} }
fn write<T: AsBytes>(&mut self, at: FilePointer, data: T) { unsafe fn write<T: AsBytes>(&mut self, at: FilePointer, data: T) {
self.write_range(at.range(size_of::<T>() as u64), data) self.write_range(at.range(size_of::<T>() as u64), data)
} }
fn write_range<T: AsBytes>(&mut self, range: FileRange, data: T) { unsafe fn write_range<T: AsBytes>(&mut self, range: FileRange, data: T) {
LayoutVerified::<_, T>::new(&mut self.map[range.as_range()]) LayoutVerified::<_, T>::new(&mut self.map[range.as_range()])
.unwrap() .unwrap()
.write(data) .write(data)
} }
fn modify<T: FromBytes + AsBytes>(&mut self, at: FilePointer) -> &mut T { unsafe fn modify<T: FromBytes + AsBytes>(&mut self, at: FilePointer) -> &mut T {
self.modify_range(at.range(size_of::<T>() as u64)) self.modify_range(at.range(size_of::<T>() as u64))
} }
fn modify_range<T: FromBytes + AsBytes>(&mut self, range: FileRange) -> &mut T { unsafe fn modify_range<T: FromBytes + AsBytes>(&mut self, range: FileRange) -> &mut T {
LayoutVerified::<_, T>::new(&mut self.map[range.as_range()]) LayoutVerified::<_, T>::new(&mut self.map[range.as_range()])
.unwrap() .unwrap()
.into_mut() .into_mut()
} }
fn reference<T: FromBytes + AsBytes>(&self, at: FilePointer) -> &T { unsafe fn reference<T: FromBytes + AsBytes>(&self, at: FilePointer) -> &T {
self.reference_range(at.range(size_of::<T>() as u64)) self.reference_range(at.range(size_of::<T>() as u64))
} }
fn reference_range<T: FromBytes + AsBytes>(&self, range: FileRange) -> &T { unsafe fn reference_range<T: FromBytes + AsBytes>(&self, range: FileRange) -> &T {
LayoutVerified::<_, T>::new(&self.map[range.as_range()]) LayoutVerified::<_, T>::new(&self.map[range.as_range()])
.unwrap() .unwrap()
.into_ref() .into_ref()
@ -264,13 +300,11 @@ impl Db {
unsafe { Mmap::map(&self.file) }.unwrap() unsafe { Mmap::map(&self.file) }.unwrap()
} }
fn add_pages(&mut self, n: u64) -> FilePointer { fn add_pages(&mut self, n: u64) -> PagePointer {
let len = self.file.metadata().unwrap().len(); let len = self.file.metadata().unwrap().len();
self.file.set_len(len + PAGE_SIZE * n).unwrap(); self.file.set_len(len + PAGE_SIZE * n).unwrap();
self.remap(); self.remap();
PagePointer::nth((len / PAGE_SIZE).try_into().unwrap())
FilePointer::null() + len
} }
pub fn new(file: File) -> Self { pub fn new(file: File) -> Self {
@ -291,11 +325,13 @@ impl Db {
header: Header::default(), header: Header::default(),
}; };
if len == 0 { unsafe {
db.init_allocator(); if len == 0 {
db.write(Self::header_ptr(), db.header); db.init_allocator();
} else { db.write(Self::header_ptr(), db.header);
db.header = db.read(Self::header_ptr()); } else {
db.header = db.read(Self::header_ptr());
}
} }
let _ = db.state.swap(Arc::new(Snapshot { let _ = db.state.swap(Arc::new(Snapshot {
@ -306,7 +342,7 @@ impl Db {
db db
} }
fn init_allocator(&mut self) { unsafe fn init_allocator(&mut self) {
let allocator_state = self.header.allocator_state; let allocator_state = self.header.allocator_state;
allocator_state.slabs.init( allocator_state.slabs.init(
self, self,
@ -326,18 +362,31 @@ mod tests {
fn it_works() { fn it_works() {
let mut db = Db::new(tempfile::tempfile().unwrap()); let mut db = Db::new(tempfile::tempfile().unwrap());
let slab = db.add_slab(16); fn alloc_and_free_many<const N: usize>(db: &mut Db, n: u64) {
let slab = db.add_slab(N as u32);
for i in 1..520 { let mut ranges = Vec::new();
let range = slab.get(&mut db); for i in 1..n {
let start = range.start.0.get(); let range = slab.alloc(db);
dbg!(start);
let data = [0; 16].map(|_| i as u8); let data = [0; N].map(|_| i as u8);
db.write_range(range, data); unsafe {
db.write_range(range, data);
}
ranges.push(range);
}
for range in ranges.into_iter().rev() {
slab.free(db, range);
}
} }
alloc_and_free_many::<1>(&mut db, 3 * PAGE_SIZE);
// alloc_and_free_many::<4>(&mut db, PAGE_SIZE / 4 * 3);
// alloc_and_free_many::<16>(&mut db, PAGE_SIZE / 16 * 3);
let mut child = std::process::Command::new("hexdump") let mut child = std::process::Command::new("hexdump")
.arg("-C") .arg("-C")
.stdin(Stdio::piped()) .stdin(Stdio::piped())