//! A counting allocator for measuring memory (`--features alloc-stats` on //! the `oxy` binary, with `OXY_TIMINGS=1`): each timing phase then also //! reports live bytes, the peak so far and what the phase allocated, so a //! profile names the structure that holds the memory. use std::alloc::{GlobalAlloc, Layout, System}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}; static LIVE: AtomicUsize = AtomicUsize::new(0); static PEAK: AtomicUsize = AtomicUsize::new(0); static TOTAL: AtomicUsize = AtomicUsize::new(0); static COUNT: AtomicUsize = AtomicUsize::new(0); static ACTIVE: AtomicBool = AtomicBool::new(false); /// The system allocator, counting. Install it with /// `#[global_allocator] static A: Counting = Counting;`. pub struct Counting; impl Counting { fn grew(n: usize) { ACTIVE.store(true, Relaxed); let live = LIVE.fetch_add(n, Relaxed) + n; TOTAL.fetch_add(n, Relaxed); COUNT.fetch_add(1, Relaxed); PEAK.fetch_max(live, Relaxed); } } unsafe impl GlobalAlloc for Counting { unsafe fn alloc(&self, l: Layout) -> *mut u8 { let p = System.alloc(l); if !p.is_null() { Self::grew(l.size()); } p } unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 { let p = System.alloc_zeroed(l); if !p.is_null() { Self::grew(l.size()); } p } unsafe fn dealloc(&self, p: *mut u8, l: Layout) { System.dealloc(p, l); LIVE.fetch_sub(l.size(), Relaxed); } unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 { let q = System.realloc(p, l, new); if !q.is_null() { LIVE.fetch_sub(l.size(), Relaxed); Self::grew(new); } q } } /// What the counting allocator has seen, when it is installed. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Stats { /// Bytes allocated and not yet freed. pub live: usize, /// The most `live` has been. pub peak: usize, /// Bytes ever allocated. pub total: usize, /// Allocations ever made. pub count: usize, } /// The counters, or `None` when the counting allocator is not installed. pub fn stats() -> Option { ACTIVE.load(Relaxed).then(|| Stats { live: LIVE.load(Relaxed), peak: PEAK.load(Relaxed), total: TOTAL.load(Relaxed), count: COUNT.load(Relaxed), }) } /// Give pages the program has freed back to the system. glibc keeps freed /// memory in each thread's arena; after a large merge renders thousands of /// files on many threads, that kept memory is most of the process's size /// (#25). Elsewhere it does nothing. pub fn release_freed_memory() { #[cfg(all(target_os = "linux", target_env = "gnu"))] // SAFETY: malloc_trim only returns free pages to the system; it is // safe to call at any time from any thread. unsafe { libc::malloc_trim(0); } }