mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 04:27:30 -06:00
- memory::budget (default min(25% RAM, 1.5 GiB); LIGHTCRAFT_MEMORY_MB, app.memoryBudget): half for engine caches (sources + rendered previews, LRU across them via a global use tick; the photo on screen stays), a quarter for decodes in flight (WorkGate: thumbnails, prefetch and import probes wait; loupe/export loads never wait), an eighth for the GPU free pool. - GPU: best-fit buffer reuse (<= 25% slack), buffers released outside a render are pooled at once (no stranded retired lists), pool trimmed after 3 s idle. - Decodes drop file bytes and raw samples early, colour/crop/orient in place. - macOS: apps install malloc_zone_pressure_relief as the release hook (after each decode, import and idle trim): freed large blocks no longer stay resident. 14 raws + loupe + 12 steps (M4 Pro): peak RSS 1.9-2.3 GB -> 0.83-0.99 GB, footprint 2.5-2.6 GB -> 0.91-0.96 GB, heap peak 930 -> 656 MB; stepping median ~13 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
3.2 KiB
Rust
118 lines
3.2 KiB
Rust
//! A cost-bounded least-recently-used map.
|
|
|
|
use std::collections::{BTreeMap, HashMap};
|
|
use std::hash::Hash;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
|
|
static TICK: AtomicU64 = AtomicU64::new(1);
|
|
|
|
/// A process-wide use counter: entries of different caches (LRUs or not) compare by recency, so
|
|
/// a memory budget can evict the least recently used entry across all of them.
|
|
pub fn next_tick() -> u64 {
|
|
TICK.fetch_add(1, Ordering::Relaxed)
|
|
}
|
|
|
|
pub struct Lru<K, V> {
|
|
map: HashMap<K, (V, usize, u64)>,
|
|
/// Use tick ([`next_tick`], shared by every LRU) → key, oldest first.
|
|
order: BTreeMap<u64, K>,
|
|
cost: usize,
|
|
budget: usize,
|
|
}
|
|
|
|
impl<K: Eq + Hash + Clone, V> Lru<K, V> {
|
|
/// An LRU holding at most `budget` cost units (e.g. bytes). The most recent entry is always
|
|
/// kept, even if it alone exceeds the budget.
|
|
pub fn new(budget: usize) -> Self {
|
|
Lru { map: HashMap::new(), order: BTreeMap::new(), cost: 0, budget }
|
|
}
|
|
|
|
fn touch(&mut self, k: &K) {
|
|
let t = next_tick();
|
|
if let Some(e) = self.map.get_mut(k) {
|
|
self.order.remove(&e.2);
|
|
e.2 = t;
|
|
self.order.insert(t, k.clone());
|
|
}
|
|
}
|
|
|
|
pub fn get(&mut self, k: &K) -> Option<&V> {
|
|
if !self.map.contains_key(k) {
|
|
return None;
|
|
}
|
|
self.touch(k);
|
|
self.map.get(k).map(|e| &e.0)
|
|
}
|
|
|
|
pub fn peek(&self, k: &K) -> Option<&V> {
|
|
self.map.get(k).map(|e| &e.0)
|
|
}
|
|
|
|
/// Use tick of the least recently used entry.
|
|
pub fn oldest_tick(&self) -> Option<u64> {
|
|
self.order.first_key_value().map(|(t, _)| *t)
|
|
}
|
|
|
|
/// Remove the least recently used entry; returns its cost.
|
|
pub fn pop_oldest(&mut self) -> Option<usize> {
|
|
let (_, k) = self.order.pop_first()?;
|
|
let (_, c, _) = self.map.remove(&k)?;
|
|
self.cost -= c;
|
|
Some(c)
|
|
}
|
|
|
|
pub fn contains(&self, k: &K) -> bool {
|
|
self.map.contains_key(k)
|
|
}
|
|
|
|
pub fn insert(&mut self, k: K, v: V, cost: usize) {
|
|
self.remove(&k);
|
|
let t = next_tick();
|
|
self.order.insert(t, k.clone());
|
|
self.map.insert(k, (v, cost, t));
|
|
self.cost += cost;
|
|
while self.cost > self.budget && self.map.len() > 1 {
|
|
let Some((_, old)) = self.order.pop_first() else { break };
|
|
if let Some((_, c, _)) = self.map.remove(&old) {
|
|
self.cost -= c;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn remove(&mut self, k: &K) -> Option<V> {
|
|
let (v, c, t) = self.map.remove(k)?;
|
|
self.order.remove(&t);
|
|
self.cost -= c;
|
|
Some(v)
|
|
}
|
|
|
|
pub fn retain(&mut self, mut f: impl FnMut(&K) -> bool) {
|
|
let drop: Vec<K> = self.map.keys().filter(|k| !f(k)).cloned().collect();
|
|
for k in drop {
|
|
self.remove(&k);
|
|
}
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
self.map.clear();
|
|
self.order.clear();
|
|
self.cost = 0;
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.map.len()
|
|
}
|
|
pub fn is_empty(&self) -> bool {
|
|
self.map.is_empty()
|
|
}
|
|
pub fn cost(&self) -> usize {
|
|
self.cost
|
|
}
|
|
pub fn budget(&self) -> usize {
|
|
self.budget
|
|
}
|
|
pub fn set_budget(&mut self, budget: usize) {
|
|
self.budget = budget;
|
|
}
|
|
}
|