// Copyright 2025 System76 // 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> = 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) { 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. #[inline] pub fn limit_mmap_threshold(threshold: i32) { unsafe { mallopt(M_MMAP_THRESHOLD, threshold as c_int); } }