perf(malloc): throttle malloc_trim to 1 Hz in hot paths
malloc_trim(0) was called at the end of every update() and view(), reaching 60-200 Hz during typical scrolling, resize, or animation. Each call walks the glibc heap (10 us to several ms depending on fragmentation) and could consume a substantial fraction of the frame budget in worst cases. Throttle trim() to once per second using a thread-local Instant, preserving the existing API. RSS stays bounded (1 Hz is enough to release collectable pages soon after) while per-frame cost becomes a single thread-local check plus a duration comparison. No call-site changes required; the three existing trim(0) invocations in src/app/cosmic.rs (update, view multi-window, view single-window) now fall under the throttle transparently.
This commit is contained in:
parent
95756b1a57
commit
77262dd0af
1 changed files with 31 additions and 3 deletions
|
|
@ -1,21 +1,49 @@
|
|||
// Copyright 2025 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::os::raw::c_int;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const M_MMAP_THRESHOLD: c_int = -3;
|
||||
|
||||
/// Minimum interval between two actual `malloc_trim` calls.
|
||||
///
|
||||
/// `trim` is called at the end of every `update()` and `view()`, which can
|
||||
/// reach 60-200 Hz during typical scrolling, resize, or animation. Each
|
||||
/// `malloc_trim` walks the glibc heap (10 µs to several ms depending on
|
||||
/// fragmentation), so calling it at render frequency can consume a
|
||||
/// substantial fraction of the frame budget. Throttling to 1 Hz keeps RSS
|
||||
/// bounded while removing the per-frame syscall from the hot path.
|
||||
const TRIM_MIN_INTERVAL: Duration = Duration::from_millis(1000);
|
||||
|
||||
thread_local! {
|
||||
static LAST_TRIM: Cell<Option<Instant>> = const { Cell::new(None) };
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
fn malloc_trim(pad: usize);
|
||||
|
||||
fn mallopt(param: c_int, value: c_int) -> c_int;
|
||||
}
|
||||
|
||||
/// Throttled wrapper over `malloc_trim`. Safe to call at render frequency:
|
||||
/// consecutive calls within `TRIM_MIN_INTERVAL` (per-thread) skip the syscall.
|
||||
#[inline]
|
||||
pub fn trim(pad: usize) {
|
||||
unsafe {
|
||||
malloc_trim(pad);
|
||||
}
|
||||
LAST_TRIM.with(|last| {
|
||||
let now = Instant::now();
|
||||
let should_trim = match last.get() {
|
||||
None => true,
|
||||
Some(prev) => now.duration_since(prev) >= TRIM_MIN_INTERVAL,
|
||||
};
|
||||
if should_trim {
|
||||
last.set(Some(now));
|
||||
unsafe {
|
||||
malloc_trim(pad);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Prevents glibc from hoarding memory via memory fragmentation.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue