perf: throttle malloc_trim + avoid VecDeque clones (squashed)
Squash of 2 yoda commits: -77262dd0perf(malloc): throttle malloc_trim to 1 Hz in hot paths -1d98eee6perf(widget): avoid VecDeque clone in segmented_button/table Model::clear
This commit is contained in:
parent
20dfd7c236
commit
6219084eb6
3 changed files with 39 additions and 5 deletions
|
|
@ -1,21 +1,49 @@
|
||||||
// Copyright 2025 System76 <info@system76.com>
|
// Copyright 2025 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
const M_MMAP_THRESHOLD: c_int = -3;
|
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" {
|
unsafe extern "C" {
|
||||||
fn malloc_trim(pad: usize);
|
fn malloc_trim(pad: usize);
|
||||||
|
|
||||||
fn mallopt(param: c_int, value: c_int) -> c_int;
|
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]
|
#[inline]
|
||||||
pub fn trim(pad: usize) {
|
pub fn trim(pad: usize) {
|
||||||
unsafe {
|
LAST_TRIM.with(|last| {
|
||||||
malloc_trim(pad);
|
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.
|
/// Prevents glibc from hoarding memory via memory fragmentation.
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,10 @@ where
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
for entity in self.order.clone() {
|
// `remove()` mutates `self.order`, so transfer ownership first:
|
||||||
|
// the inner `self.order.remove(index)` then no-ops because
|
||||||
|
// `position()` can't find the entity in the empty VecDeque.
|
||||||
|
for entity in std::mem::take(&mut self.order) {
|
||||||
self.remove(entity);
|
self.remove(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,10 @@ where
|
||||||
/// model.clear();
|
/// model.clear();
|
||||||
/// ```
|
/// ```
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
for entity in self.order.clone() {
|
// `remove()` mutates `self.order`, so transfer ownership first:
|
||||||
|
// the inner `self.order.remove(index)` then no-ops because
|
||||||
|
// `position()` can't find the entity in the empty VecDeque.
|
||||||
|
for entity in std::mem::take(&mut self.order) {
|
||||||
self.remove(entity);
|
self.remove(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue