store: switch in-memory cache to LRU-based HashMap to cap memory usage

I just choose "clru" because it already exists in our dependency tree thorough
gix. I don't think LRU is the best cache eviction policy for our use case (a
simpler FIFO-based one might be good enough?), but it wouldn't matter for CLI
or GUI use case. I don't see significant performance degradation with "jj log
--stat -n1000".

RwLock is replaced with Mutex since get() is inherently a mutable operation.
This commit is contained in:
Yuya Nishihara 2024-08-29 19:58:27 +09:00
parent 424623ba91
commit db6a58d315

View file

@ -15,14 +15,14 @@
#![allow(missing_docs)]
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::io::Read;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::Mutex;
use std::time::SystemTime;
use clru::CLruCache;
use futures::stream::BoxStream;
use pollster::FutureExt;
@ -49,13 +49,18 @@ use crate::signing::Signer;
use crate::tree::Tree;
use crate::tree_builder::TreeBuilder;
// There are more tree objects than commits, and trees are often shared across
// commits.
const COMMIT_CACHE_CAPACITY: usize = 100;
const TREE_CACHE_CAPACITY: usize = 1000;
/// Wraps the low-level backend and makes it return more convenient types. Also
/// adds caching.
pub struct Store {
backend: Box<dyn Backend>,
signer: Signer,
commit_cache: RwLock<HashMap<CommitId, Arc<backend::Commit>>>,
tree_cache: RwLock<HashMap<(RepoPathBuf, TreeId), Arc<backend::Tree>>>,
commit_cache: Mutex<CLruCache<CommitId, Arc<backend::Commit>>>,
tree_cache: Mutex<CLruCache<(RepoPathBuf, TreeId), Arc<backend::Tree>>>,
}
impl Debug for Store {
@ -71,8 +76,8 @@ impl Store {
Arc::new(Store {
backend,
signer,
commit_cache: Default::default(),
tree_cache: Default::default(),
commit_cache: Mutex::new(CLruCache::new(COMMIT_CACHE_CAPACITY.try_into().unwrap())),
tree_cache: Mutex::new(CLruCache::new(TREE_CACHE_CAPACITY.try_into().unwrap())),
})
}
@ -136,15 +141,15 @@ impl Store {
async fn get_backend_commit(&self, id: &CommitId) -> BackendResult<Arc<backend::Commit>> {
{
let read_locked_cached = self.commit_cache.read().unwrap();
if let Some(data) = read_locked_cached.get(id).cloned() {
let mut locked_cache = self.commit_cache.lock().unwrap();
if let Some(data) = locked_cache.get(id).cloned() {
return Ok(data);
}
}
let commit = self.backend.read_commit(id).await?;
let data = Arc::new(commit);
let mut write_locked_cache = self.commit_cache.write().unwrap();
write_locked_cache.insert(id.clone(), data.clone());
let mut locked_cache = self.commit_cache.lock().unwrap();
locked_cache.put(id.clone(), data.clone());
Ok(data)
}
@ -158,8 +163,8 @@ impl Store {
let (commit_id, commit) = self.backend.write_commit(commit, sign_with)?;
let data = Arc::new(commit);
{
let mut write_locked_cache = self.commit_cache.write().unwrap();
write_locked_cache.insert(commit_id.clone(), data.clone());
let mut locked_cache = self.commit_cache.lock().unwrap();
locked_cache.put(commit_id.clone(), data.clone());
}
Ok(Commit::new(self.clone(), commit_id, data))
@ -185,15 +190,15 @@ impl Store {
) -> BackendResult<Arc<backend::Tree>> {
let key = (dir.to_owned(), id.clone());
{
let read_locked_cache = self.tree_cache.read().unwrap();
if let Some(data) = read_locked_cache.get(&key).cloned() {
let mut locked_cache = self.tree_cache.lock().unwrap();
if let Some(data) = locked_cache.get(&key).cloned() {
return Ok(data);
}
}
let data = self.backend.read_tree(dir, id).await?;
let data = Arc::new(data);
let mut write_locked_cache = self.tree_cache.write().unwrap();
write_locked_cache.insert(key, data.clone());
let mut locked_cache = self.tree_cache.lock().unwrap();
locked_cache.put(key, data.clone());
Ok(data)
}
@ -218,8 +223,8 @@ impl Store {
let tree_id = self.backend.write_tree(path, &tree)?;
let data = Arc::new(tree);
{
let mut write_locked_cache = self.tree_cache.write().unwrap();
write_locked_cache.insert((path.to_owned(), tree_id.clone()), data.clone());
let mut locked_cache = self.tree_cache.lock().unwrap();
locked_cache.put((path.to_owned(), tree_id.clone()), data.clone());
}
Ok(Tree::new(self.clone(), path.to_owned(), tree_id, data))