feat(memory): scale L1 audio cache cap to host memory profile (#331)
Add audio_cache_l1_max_bytes to MemoryProfile and wire it into AppState's AudioCache constructor. Normal hosts keep the historical 400 MB cap; LowMemory hosts drop to 50 MB. Without this, a Pi 3B (1 GB total) starts with the L1 cache cap set at ~40 % of physical RAM. Even with the Feature #3 prefetch caps already in place, the runtime watchdog from the previous commit ends up firing more often than necessary because nothing prevents a single L1 fill cycle from claiming 400 MB. Capping at 50 MB caps how much the cache can grow before either: (a) eviction picks up naturally, or (b) the watchdog steps in if the rest of the system is also under pressure. Pure refactor in lib.rs — replaces a hard-coded 400 * 1024 * 1024 with a profile field read. The default-constructed AudioCache fallback (when disk-spillover init fails) keeps its own crate default; no need to thread the profile there since the failure path is rare. Two tests added: a presence-bound assertion in the existing Pi 3B case, and an explicit comparison that LowMemory's cap is strictly smaller than Normal's. cc
This commit is contained in:
2 files changed
+24
-2
No files matched your search
@@ -43,6 +43,11 @@ pub struct MemoryProfile {
|
|||||||
/// (44.1 kHz / 16-bit FLAC) so each cached track stays under ~15 MB
|
/// (44.1 kHz / 16-bit FLAC) so each cached track stays under ~15 MB
|
||||||
/// instead of ~60 MB.
|
/// instead of ~60 MB.
|
||||||
pub allow_hires_prefetch: bool,
|
pub allow_hires_prefetch: bool,
|
||||||
|
/// Upper bound for the L1 (in-memory) audio cache. The default
|
||||||
|
/// 400 MB cap is sized for Normal-class desktops; on a Pi 3B (1 GB
|
||||||
|
/// total) that single subsystem could consume 40 % of RAM, which
|
||||||
|
/// guarantees swap thrash before the watchdog can react.
|
||||||
|
pub audio_cache_l1_max_bytes: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryProfile {
|
impl MemoryProfile {
|
||||||
@@ -61,6 +66,7 @@ impl MemoryProfile {
|
|||||||
max_initial_buffer_bytes: 2 * 1024 * 1024,
|
max_initial_buffer_bytes: 2 * 1024 * 1024,
|
||||||
max_concurrent_prefetch: 2,
|
max_concurrent_prefetch: 2,
|
||||||
allow_hires_prefetch: true,
|
allow_hires_prefetch: true,
|
||||||
|
audio_cache_l1_max_bytes: 400 * 1024 * 1024,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
@@ -70,6 +76,7 @@ impl MemoryProfile {
|
|||||||
max_initial_buffer_bytes: 256 * 1024,
|
max_initial_buffer_bytes: 256 * 1024,
|
||||||
max_concurrent_prefetch: 1,
|
max_concurrent_prefetch: 1,
|
||||||
allow_hires_prefetch: false,
|
allow_hires_prefetch: false,
|
||||||
|
audio_cache_l1_max_bytes: 50 * 1024 * 1024,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,6 +245,17 @@ SwapTotal: 2097152 kB
|
|||||||
assert_eq!(profile.max_concurrent_prefetch, 1);
|
assert_eq!(profile.max_concurrent_prefetch, 1);
|
||||||
assert!(!profile.allow_hires_prefetch);
|
assert!(!profile.allow_hires_prefetch);
|
||||||
assert!(profile.max_initial_buffer_bytes <= 256 * 1024);
|
assert!(profile.max_initial_buffer_bytes <= 256 * 1024);
|
||||||
|
// L1 cap must be a small fraction of total RAM — at most ~10 %
|
||||||
|
// of a Pi 3B, so we don't reserve four-tenths of memory for one
|
||||||
|
// subsystem on a 1 GB host.
|
||||||
|
assert!(profile.audio_cache_l1_max_bytes <= 100 * 1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn audio_cache_cap_is_significantly_smaller_on_low_memory() {
|
||||||
|
let normal = MemoryProfile::from_total_kb(8 * 1024 * 1024);
|
||||||
|
let low = MemoryProfile::from_total_kb(938196);
|
||||||
|
assert!(low.audio_cache_l1_max_bytes < normal.audio_cache_l1_max_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -134,9 +134,13 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create audio cache (L1 - memory, 400MB) with optional disk spillover
|
// Create audio cache (L1 - memory) with optional disk spillover.
|
||||||
|
// The L1 cap is sized per host: Normal hosts get the historical
|
||||||
|
// 400 MB; LowMemory hosts (issue #331) drop to 50 MB so the cache
|
||||||
|
// alone can never claim 40 % of a 1 GB Pi.
|
||||||
|
let l1_cap = qbz_core::system_capabilities::memory_profile().audio_cache_l1_max_bytes;
|
||||||
let audio_cache = if let Some(pc) = playback_cache {
|
let audio_cache = if let Some(pc) = playback_cache {
|
||||||
Arc::new(AudioCache::with_playback_cache(400 * 1024 * 1024, pc))
|
Arc::new(AudioCache::with_playback_cache(l1_cap, pc))
|
||||||
} else {
|
} else {
|
||||||
Arc::new(AudioCache::default())
|
Arc::new(AudioCache::default())
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user