Skip to content

Commit aff52fb

Browse files
committed
Using an unrolled linked list to halve memory usage
1 parent 187486f commit aff52fb

8 files changed

Lines changed: 324 additions & 142 deletions

File tree

src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::sync::Arc;
23

34
mod block_read_write;
45

@@ -28,6 +29,56 @@ impl<'a> Record<'a> {
2829
}
2930
}
3031

32+
#[derive(Clone, Default, Debug, Ord, PartialOrd, Eq, PartialEq)]
33+
pub struct FileNumber {
34+
file_number: Arc<u64>,
35+
}
36+
37+
impl FileNumber {
38+
fn new(file_number: u64) -> Self {
39+
FileNumber {
40+
file_number: Arc::new(file_number),
41+
}
42+
}
43+
44+
/// Returns whether there is no clone of this FileNumber in existance.
45+
///
46+
/// /!\ care should be taken to not have some other code store a &FileNumber which could alias
47+
/// with self as it might then be sementically incorrect to delete content based only on this
48+
/// returning `true`.
49+
pub fn can_be_deleted(&self) -> bool {
50+
Arc::strong_count(&self.file_number) == 1
51+
}
52+
53+
#[cfg(test)]
54+
pub fn unroll(&self, tracker: &crate::rolling::FileTracker) -> Vec<u64> {
55+
let mut file = self.clone();
56+
let mut file_numbers = Vec::new();
57+
loop {
58+
file_numbers.push(file.file_number());
59+
if let Some(next_file) = tracker.next(&file) {
60+
file = next_file;
61+
} else {
62+
return file_numbers;
63+
}
64+
}
65+
}
66+
67+
pub fn filename(&self) -> String {
68+
format!("wal-{:020}", self.file_number)
69+
}
70+
71+
#[cfg(test)]
72+
pub fn file_number(&self) -> u64 {
73+
*self.file_number
74+
}
75+
76+
#[cfg(test)]
77+
pub fn for_test(file_number: u64) -> Self {
78+
FileNumber::new(file_number)
79+
}
80+
}
81+
3182
/// Resources used by mrecordlog
3283
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3384
pub struct ResourceUsage {

src/mem/arena.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
pub const PAGE_SIZE: usize = 1 << 20;
2+
3+
// TODO make it an array once we get a way to allocate array on the heap.
4+
pub type Page = Box<[u8]>;
5+
6+
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
7+
pub struct PageId(usize);
8+
9+
/// An arena of fixed sized pages.
10+
#[derive(Default)]
11+
pub struct Arena {
12+
/// We use an array to store the list of pages.
13+
/// It can be seen as an efficient map from page id to pages.
14+
///
15+
/// This map's len can-only grows. Its size is therefore the maximum number of pages
16+
/// that was ever allocated. One page being 1MB long, this is not a problem.
17+
///
18+
/// If a page is not allocated, the corresponding entry is `None`.
19+
pages: Vec<Option<Page>>,
20+
/// `free_slots` slots keeps track of the pages that are not allocated.
21+
free_slots: Vec<PageId>,
22+
/// `free_page_ids` keeps track of the allocated pages that are
23+
/// available.
24+
free_page_ids: Vec<PageId>,
25+
}
26+
27+
impl Arena {
28+
/// Returns an allocated page id.
29+
pub fn get_page_id(&mut self) -> PageId {
30+
if let Some(page_id) = self.free_page_ids.pop() {
31+
assert!(self.pages[page_id.0].is_some());
32+
return page_id;
33+
}
34+
let page: Page = vec![0u8; PAGE_SIZE].into_boxed_slice();
35+
if let Some(free_slot) = self.free_slots.pop() {
36+
let slot = &mut self.pages[free_slot.0];
37+
assert!(slot.is_none());
38+
*slot = Some(page);
39+
return free_slot;
40+
} else {
41+
let new_page_id = self.pages.len();
42+
self.pages.push(Some(page));
43+
PageId(new_page_id)
44+
}
45+
}
46+
47+
#[inline]
48+
pub fn page(&self, page_id: PageId) -> &[u8] {
49+
self.pages[page_id.0].as_ref().unwrap()
50+
}
51+
52+
#[inline]
53+
pub fn page_mut(&mut self, page_id: PageId) -> &mut [u8] {
54+
self.pages[page_id.0].as_mut().unwrap()
55+
}
56+
57+
pub fn release_page(&mut self, page_id: PageId) {
58+
self.free_page_ids.push(page_id);
59+
assert!(self.pages[page_id.0].is_some());
60+
if self.free_page_ids.len() > self.num_allocated_pages() {
61+
self.gc();
62+
}
63+
}
64+
65+
/// `gc` releases memory by deallocating ALL of the free pages.
66+
pub fn gc(&mut self) {
67+
for free_page_id in self.free_page_ids.drain(..) {
68+
self.pages[free_page_id.0] = None;
69+
self.free_slots.push(free_page_id);
70+
}
71+
}
72+
73+
pub fn num_allocated_pages(&self) -> usize {
74+
self.pages.len() - self.free_slots.len()
75+
}
76+
77+
pub fn capacity(&self) -> usize {
78+
self.num_allocated_pages() * PAGE_SIZE
79+
}
80+
}
81+
82+
83+
#[cfg(test)]
84+
mod tests {
85+
use super::*;
86+
87+
#[test]
88+
fn test_arena_simple() {
89+
let mut arena = Arena::default();
90+
assert_eq!(arena.capacity(), 0);
91+
assert_eq!(arena.get_page_id(), PageId(0));
92+
assert_eq!(arena.get_page_id(), PageId(1));
93+
arena.release_page(PageId(0));
94+
assert_eq!(arena.get_page_id(), PageId(0));
95+
}
96+
97+
#[test]
98+
fn test_arena_gc() {
99+
let mut arena = Arena::default();
100+
assert_eq!(arena.capacity(), 0);
101+
assert_eq!(arena.get_page_id(), PageId(0));
102+
assert_eq!(arena.get_page_id(), PageId(1));
103+
arena.release_page(PageId(1));
104+
assert_eq!(arena.num_allocated_pages(), 2);
105+
arena.gc();
106+
assert_eq!(arena.num_allocated_pages(), 1);
107+
assert_eq!(arena.get_page_id(), PageId(1));
108+
assert_eq!(arena.num_allocated_pages(), 2);
109+
}
110+
}

src/mem/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
mod queue;
22
mod queues;
3+
mod arena;
34

5+
6+
use self::arena::{Arena, PAGE_SIZE};
47
pub(crate) use self::queue::MemQueue;
58
pub(crate) use self::queues::MemQueues;
69

0 commit comments

Comments
 (0)