Skip to content

Commit

Permalink
Add a freelist cache
Browse files Browse the repository at this point in the history
This should reduce read requests for the next freelist item in the
benchmark.
  • Loading branch information
rkuris committed Sep 10, 2024
1 parent fc1e43c commit af765d9
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 12 deletions.
3 changes: 3 additions & 0 deletions benchmark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ async fn main() -> Result<(), Box<dyn Error>> {

let mgrcfg = RevisionManagerConfig::builder()
.node_cache_size(args.cache_size)
.free_list_cache_size(
NonZeroUsize::new(2 * args.batch_size as usize).expect("batch size > 0"),
)
.max_revisions(args.revisions)
.build();
let cfg = DbConfig::builder()
Expand Down
10 changes: 9 additions & 1 deletion firewood/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub struct RevisionManagerConfig {

#[builder(default_code = "NonZero::new(20480).expect(\"non-zero\")")]
node_cache_size: NonZero<usize>,

#[builder(default_code = "NonZero::new(10000).expect(\"non-zero\")")]
free_list_cache_size: NonZero<usize>,
}

type CommittedRevision = Arc<NodeStore<Committed, FileBacked>>;
Expand Down Expand Up @@ -62,7 +65,12 @@ impl RevisionManager {
truncate: bool,
config: RevisionManagerConfig,
) -> Result<Self, Error> {
let storage = Arc::new(FileBacked::new(filename, config.node_cache_size, truncate)?);
let storage = Arc::new(FileBacked::new(
filename,
config.node_cache_size,
config.free_list_cache_size,
truncate,
)?);
let nodestore = match truncate {
true => Arc::new(NodeStore::new_empty_committed(storage.clone())?),
false => Arc::new(NodeStore::open(storage.clone())?),
Expand Down
22 changes: 17 additions & 5 deletions storage/src/linear/filebacked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ use super::{ReadableStorage, WritableStorage};
pub struct FileBacked {
fd: Mutex<File>,
cache: Mutex<LruCache<LinearAddress, Arc<Node>>>,
free_list_cache: Mutex<LruCache<LinearAddress, Option<LinearAddress>>>,
}

impl FileBacked {
/// Create or open a file at a given path
pub fn new(
path: PathBuf,
node_cache_size: NonZero<usize>,
free_list_cache_size: NonZero<usize>,
truncate: bool,
) -> Result<Self, Error> {
let fd = OpenOptions::new()
Expand All @@ -47,6 +49,7 @@ impl FileBacked {
Ok(Self {
fd: Mutex::new(fd),
cache: Mutex::new(LruCache::new(node_cache_size)),
free_list_cache: Mutex::new(LruCache::new(free_list_cache_size)),
})
}
}
Expand All @@ -68,11 +71,15 @@ impl ReadableStorage for FileBacked {
fn read_cached_node(&self, addr: LinearAddress) -> Option<Arc<Node>> {
let mut guard = self.cache.lock().expect("poisoned lock");
let cached = guard.get(&addr).cloned();
if cached.is_some() {
counter!("firewood.node.cache.hit").increment(1);
} else {
counter!("firewood.node.cache.miss").increment(1);
}
counter!("firewood.cache.node", "type" => if cached.is_some() { "hit" } else { "miss" })
.increment(1);
cached
}

fn free_list_cache(&self, addr: LinearAddress) -> Option<Option<LinearAddress>> {
let mut guard = self.free_list_cache.lock().expect("poisoned lock");
let cached = guard.pop(&addr);
counter!("firewood.cache.freelist", "type" => if cached.is_some() { "hit" } else { "miss" }).increment(1);
cached
}
}
Expand Down Expand Up @@ -102,4 +109,9 @@ impl WritableStorage for FileBacked {
guard.pop(addr);
}
}

fn add_to_free_list_cache(&self, addr: LinearAddress, next: Option<LinearAddress>) {
let mut guard = self.free_list_cache.lock().expect("poisoned lock");
guard.put(addr, next);
}
}
8 changes: 8 additions & 0 deletions storage/src/linear/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ pub trait ReadableStorage: Debug + Sync + Send {
fn read_cached_node(&self, _addr: LinearAddress) -> Option<Arc<Node>> {
None
}

/// Fetch the next pointer from the freelist cache
fn free_list_cache(&self, _addr: LinearAddress) -> Option<Option<LinearAddress>> {
None
}
}

/// Trait for writable storage.
Expand All @@ -73,4 +78,7 @@ pub trait WritableStorage: ReadableStorage {

/// Invalidate all nodes that are part of a specific revision, as these will never be referenced again
fn invalidate_cached_nodes<'a>(&self, _addresses: impl Iterator<Item = &'a LinearAddress>) {}

/// Add a new entry to the freelist cache
fn add_to_free_list_cache(&self, _addr: LinearAddress, _next: Option<LinearAddress>) {}
}
19 changes: 13 additions & 6 deletions storage/src/nodestore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,17 @@ impl<S: ReadableStorage> NodeStore<Arc<ImmutableProposal>, S> {
if let Some(free_stored_area_addr) = self.header.free_lists[index] {
// Update the free list head.
// Skip the index byte and Area discriminant byte
let free_area_addr = free_stored_area_addr.get() + 2;
let free_head_stream = self.storage.stream_from(free_area_addr)?;
let free_head: FreeArea = bincode::deserialize_from(free_head_stream)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
if let Some(free_head) = self.storage.free_list_cache(free_stored_area_addr) {
self.header.free_lists[index] = free_head;
} else {
let free_area_addr = free_stored_area_addr.get() + 2;
let free_head_stream = self.storage.stream_from(free_area_addr)?;
let free_head: FreeArea = bincode::deserialize_from(free_head_stream)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))?;

// Update the free list to point to the next free block.
self.header.free_lists[index] = free_head.next_free_block;
// Update the free list to point to the next free block.
self.header.free_lists[index] = free_head.next_free_block;
}

// Return the address of the newly allocated block.
return Ok(Some((free_stored_area_addr, index as AreaIndex)));
Expand Down Expand Up @@ -462,6 +466,9 @@ impl<S: WritableStorage> NodeStore<Committed, S> {

self.storage.write(addr.into(), &stored_area_bytes)?;

self.storage
.add_to_free_list_cache(addr, self.header.free_lists[area_size_index as usize]);

// The newly freed block is now the head of the free list.
self.header.free_lists[area_size_index as usize] = Some(addr);

Expand Down

0 comments on commit af765d9

Please sign in to comment.