OXYGENHUB

Coverage / crates/oxy-core/src/alloc_stats.rs 17.4%

8 of 46 lines run, 2 of 16 functions · run #299 on main · run missed

1
//! A counting allocator for measuring memory (`--features alloc-stats` on
2
//! the `oxy` binary, with `OXY_TIMINGS=1`): each timing phase then also
3
//! reports live bytes, the peak so far and what the phase allocated, so a
4
//! profile names the structure that holds the memory.
5
6
use std::alloc::{GlobalAlloc, Layout, System};
7
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
8
9
static LIVE: AtomicUsize = AtomicUsize::new(0);
10
static PEAK: AtomicUsize = AtomicUsize::new(0);
11
static TOTAL: AtomicUsize = AtomicUsize::new(0);
12
static COUNT: AtomicUsize = AtomicUsize::new(0);
13
static ACTIVE: AtomicBool = AtomicBool::new(false);
14
15
/// The system allocator, counting. Install it with
16
/// `#[global_allocator] static A: Counting = Counting;`.
17
pub struct Counting;
18
19
impl Counting {
200×
    fn grew(n: usize) {
210×
        ACTIVE.store(true, Relaxed);
220×
        let live = LIVE.fetch_add(n, Relaxed) + n;
230×
        TOTAL.fetch_add(n, Relaxed);
240×
        COUNT.fetch_add(1, Relaxed);
250×
        PEAK.fetch_max(live, Relaxed);
260×
    }
27
}
28
29
unsafe impl GlobalAlloc for Counting {
300×
    unsafe fn alloc(&self, l: Layout) -> *mut u8 {
310×
        let p = System.alloc(l);
320×
        if !p.is_null() {
330×
            Self::grew(l.size());
340×
        }
350×
        p
360×
    }
370×
    unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 {
380×
        let p = System.alloc_zeroed(l);
390×
        if !p.is_null() {
400×
            Self::grew(l.size());
410×
        }
420×
        p
430×
    }
440×
    unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
450×
        System.dealloc(p, l);
460×
        LIVE.fetch_sub(l.size(), Relaxed);
470×
    }
480×
    unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
490×
        let q = System.realloc(p, l, new);
500×
        if !q.is_null() {
510×
            LIVE.fetch_sub(l.size(), Relaxed);
520×
            Self::grew(new);
530×
        }
540×
        q
550×
    }
56
}
57
58
/// What the counting allocator has seen, when it is installed.
59
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
60
pub struct Stats {
61
    /// Bytes allocated and not yet freed.
62
    pub live: usize,
63
    /// The most `live` has been.
64
    pub peak: usize,
65
    /// Bytes ever allocated.
66
    pub total: usize,
67
    /// Allocations ever made.
68
    pub count: usize,
69
}
70
71
/// The counters, or `None` when the counting allocator is not installed.
72311×
pub fn stats() -> Option<Stats> {
73311×
    ACTIVE.load(Relaxed).then(|| Stats {
740×
        live: LIVE.load(Relaxed),
750×
        peak: PEAK.load(Relaxed),
760×
        total: TOTAL.load(Relaxed),
770×
        count: COUNT.load(Relaxed),
780×
    })
79311×
}
80
81
/// Give pages the program has freed back to the system. glibc keeps freed
82
/// memory in each thread's arena; after a large merge renders thousands of
83
/// files on many threads, that kept memory is most of the process's size
84
/// (#25). Elsewhere it does nothing.
8524×
pub fn release_freed_memory() {
86
    #[cfg(all(target_os = "linux", target_env = "gnu"))]
87
    // SAFETY: malloc_trim only returns free pages to the system; it is
88
    // safe to call at any time from any thread.
8924×
    unsafe {
9024×
        libc::malloc_trim(0);
9124×
    }
9224×
}