Skip to main content

ModAlloc

Struct ModAlloc 

Source
pub struct ModAlloc { /* private fields */ }
Expand description

Global allocator wrapper that tracks allocations.

Install as #[global_allocator] to enable tracking. The wrapper forwards every allocation, deallocation, reallocation, and zero-initialised allocation to std::alloc::System and records the event in four lock-free AtomicU64 counters.

§Example

use mod_alloc::ModAlloc;

#[global_allocator]
static GLOBAL: ModAlloc = ModAlloc::new();

fn main() {
    let v: Vec<u8> = vec![0; 1024];
    let stats = GLOBAL.snapshot();
    assert!(stats.alloc_count >= 1);
    drop(v);
}

Implementations§

Source§

impl ModAlloc

Source

pub const fn new() -> Self

Construct a new ModAlloc allocator wrapper.

All counters start at zero. This function is const, which allows construction in a static for use as #[global_allocator].

§Example
use mod_alloc::ModAlloc;

static GLOBAL: ModAlloc = ModAlloc::new();
let stats = GLOBAL.snapshot();
assert_eq!(stats.alloc_count, 0);
Examples found in repository?
examples/basic.rs (line 8)
8static GLOBAL: ModAlloc = ModAlloc::new();
More examples
Hide additional examples
examples/bench_overhead.rs (line 20)
20static GLOBAL: ModAlloc = ModAlloc::new();
examples/backtraces.rs (line 13)
13static GLOBAL: mod_alloc::ModAlloc = mod_alloc::ModAlloc::new();
examples/symbolicate.rs (line 14)
14static GLOBAL: mod_alloc::ModAlloc = mod_alloc::ModAlloc::new();
Source

pub fn snapshot(&self) -> AllocStats

Snapshot the current counter values.

Each counter is read independently with Relaxed ordering; the resulting AllocStats is a coherent best-effort view but does not represent a single atomic moment in time. For scoped measurement, prefer Profiler.

§Example
use mod_alloc::ModAlloc;

let alloc = ModAlloc::new();
let stats = alloc.snapshot();
assert_eq!(stats.alloc_count, 0);
Examples found in repository?
examples/bench_overhead.rs (line 40)
26fn main() {
27    for _ in 0..WARMUP {
28        let v: Vec<u8> = Vec::with_capacity(SIZE);
29        std::hint::black_box(&v);
30    }
31
32    GLOBAL.reset();
33    let start = Instant::now();
34    for _ in 0..N {
35        let v: Vec<u8> = Vec::with_capacity(SIZE);
36        std::hint::black_box(&v);
37    }
38    let elapsed = start.elapsed();
39
40    let snap = GLOBAL.snapshot();
41    let per_cycle_ns = elapsed.as_nanos() as f64 / N as f64;
42
43    println!("bench_overhead:");
44    println!("  iterations:           {N}");
45    println!("  allocation size:      {SIZE} bytes");
46    println!("  elapsed:              {elapsed:?}");
47    println!("  per alloc+dealloc:    {per_cycle_ns:.1} ns");
48    println!();
49    println!("counter snapshot after run:");
50    println!("  alloc_count:   {}", snap.alloc_count);
51    println!("  total_bytes:   {}", snap.total_bytes);
52    println!("  current_bytes: {}", snap.current_bytes);
53    println!("  peak_bytes:    {}", snap.peak_bytes);
54}
More examples
Hide additional examples
examples/basic.rs (line 30)
10fn main() {
11    let p = Profiler::start();
12
13    let v: Vec<u64> = (0..1_000).collect();
14    let sum: u64 = v.iter().sum();
15    drop(v);
16
17    let mut owned: Vec<String> = Vec::with_capacity(100);
18    for i in 0..100 {
19        owned.push(format!("item-{i}"));
20    }
21    drop(owned);
22
23    let delta = p.stop();
24    println!("Profiler delta (alloc/total/current = delta; peak = absolute):");
25    println!("  alloc_count:   {}", delta.alloc_count);
26    println!("  total_bytes:   {}", delta.total_bytes);
27    println!("  current_bytes: {}", delta.current_bytes);
28    println!("  peak_bytes:    {}", delta.peak_bytes);
29
30    let snap = GLOBAL.snapshot();
31    println!();
32    println!("Process-wide snapshot:");
33    println!("  alloc_count:   {}", snap.alloc_count);
34    println!("  total_bytes:   {}", snap.total_bytes);
35    println!("  current_bytes: {}", snap.current_bytes);
36    println!("  peak_bytes:    {}", snap.peak_bytes);
37
38    println!();
39    println!("(workload checksum: {sum})");
40}
examples/backtraces.rs (line 48)
37fn main() {
38    for _ in 0..1_000 {
39        alloc_small();
40    }
41    for _ in 0..100 {
42        alloc_medium();
43    }
44    for _ in 0..10 {
45        alloc_large();
46    }
47
48    let snap = GLOBAL.snapshot();
49    println!("Process-wide snapshot:");
50    println!("  alloc_count:   {}", snap.alloc_count);
51    println!("  total_bytes:   {}", snap.total_bytes);
52    println!("  current_bytes: {}", snap.current_bytes);
53    println!("  peak_bytes:    {}", snap.peak_bytes);
54    println!();
55
56    let mut sites = GLOBAL.call_sites();
57    sites.sort_by_key(|s| std::cmp::Reverse(s.total_bytes));
58
59    println!("Top 10 call sites by total bytes:");
60    println!(
61        "{:>10}  {:>14}  {:>4}  {:>18}",
62        "count", "total_bytes", "frm", "top frame"
63    );
64    for (rank, site) in sites.iter().take(10).enumerate() {
65        println!(
66            "{rank:>2}: {count:>6}  {bytes:>14}  {frm:>4}  {top:#018x}",
67            rank = rank,
68            count = site.count,
69            bytes = site.total_bytes,
70            frm = site.frame_count,
71            top = site.frames[0],
72        );
73    }
74}
examples/symbolicate.rs (line 52)
38fn main() {
39    for _ in 0..500 {
40        alloc_small();
41    }
42    for _ in 0..100 {
43        alloc_medium();
44    }
45    for _ in 0..10 {
46        alloc_large();
47    }
48
49    let mut report = GLOBAL.symbolicated_report();
50    report.sort_by_key(|s| std::cmp::Reverse(s.total_bytes));
51
52    let snap = GLOBAL.snapshot();
53    println!("Process-wide:");
54    println!(
55        "  {} allocations, {} total bytes, peak {} bytes",
56        snap.alloc_count, snap.total_bytes, snap.peak_bytes
57    );
58    println!();
59
60    println!("Top call sites by total bytes:");
61    println!(
62        "{:>2}  {:>10}  {:>14}  top frame",
63        "#", "count", "total_bytes"
64    );
65    for (rank, site) in report.iter().take(10).enumerate() {
66        let top = &site.frames[0];
67        let name = top.function.as_deref().unwrap_or("<unresolved>");
68        let loc = match (top.file.as_ref(), top.line) {
69            (Some(f), Some(l)) => format!("  {}:{}", f.display(), l),
70            _ => String::new(),
71        };
72        println!(
73            "{rank:>2}  {count:>10}  {bytes:>14}  {name}{loc}",
74            rank = rank,
75            count = site.count,
76            bytes = site.total_bytes,
77            name = name,
78            loc = loc,
79        );
80        // Show inlined expansions if any.
81        for inlined in site.frames.iter().skip(1).take_while(|f| f.inlined) {
82            let n = inlined.function.as_deref().unwrap_or("<unresolved>");
83            println!("        inlined: {n}");
84        }
85    }
86}
Source

pub fn reset(&self)

Reset all counters to zero.

Intended for use at the start of a profile run, before any outstanding allocations exist. Calling reset while allocations are live can cause current_bytes to wrap on subsequent deallocations; the other counters are unaffected.

§Example
use mod_alloc::ModAlloc;

let alloc = ModAlloc::new();
alloc.reset();
let stats = alloc.snapshot();
assert_eq!(stats.alloc_count, 0);
Examples found in repository?
examples/bench_overhead.rs (line 32)
26fn main() {
27    for _ in 0..WARMUP {
28        let v: Vec<u8> = Vec::with_capacity(SIZE);
29        std::hint::black_box(&v);
30    }
31
32    GLOBAL.reset();
33    let start = Instant::now();
34    for _ in 0..N {
35        let v: Vec<u8> = Vec::with_capacity(SIZE);
36        std::hint::black_box(&v);
37    }
38    let elapsed = start.elapsed();
39
40    let snap = GLOBAL.snapshot();
41    let per_cycle_ns = elapsed.as_nanos() as f64 / N as f64;
42
43    println!("bench_overhead:");
44    println!("  iterations:           {N}");
45    println!("  allocation size:      {SIZE} bytes");
46    println!("  elapsed:              {elapsed:?}");
47    println!("  per alloc+dealloc:    {per_cycle_ns:.1} ns");
48    println!();
49    println!("counter snapshot after run:");
50    println!("  alloc_count:   {}", snap.alloc_count);
51    println!("  total_bytes:   {}", snap.total_bytes);
52    println!("  current_bytes: {}", snap.current_bytes);
53    println!("  peak_bytes:    {}", snap.peak_bytes);
54}
Source

pub fn call_sites(&self) -> Vec<CallSiteStats>

Available on crate feature backtraces only.

Drain the per-call-site aggregation table into a Vec.

Available only with the backtraces cargo feature. The returned vector contains one CallSiteStats per unique call site observed since the table was first written. Each row carries up to 8 raw return addresses (top of stack first), the number of allocations attributed to that site, and the total bytes.

Symbolication (resolving addresses to function names) lands in v0.9.2. This method exposes raw addresses only.

§Example
use mod_alloc::ModAlloc;

#[global_allocator]
static GLOBAL: ModAlloc = ModAlloc::new();

let _v: Vec<u8> = vec![0; 1024];
for site in GLOBAL.call_sites() {
    println!("{} allocs, {} bytes at {:#x}",
        site.count, site.total_bytes, site.frames[0]);
}
Examples found in repository?
examples/backtraces.rs (line 56)
37fn main() {
38    for _ in 0..1_000 {
39        alloc_small();
40    }
41    for _ in 0..100 {
42        alloc_medium();
43    }
44    for _ in 0..10 {
45        alloc_large();
46    }
47
48    let snap = GLOBAL.snapshot();
49    println!("Process-wide snapshot:");
50    println!("  alloc_count:   {}", snap.alloc_count);
51    println!("  total_bytes:   {}", snap.total_bytes);
52    println!("  current_bytes: {}", snap.current_bytes);
53    println!("  peak_bytes:    {}", snap.peak_bytes);
54    println!();
55
56    let mut sites = GLOBAL.call_sites();
57    sites.sort_by_key(|s| std::cmp::Reverse(s.total_bytes));
58
59    println!("Top 10 call sites by total bytes:");
60    println!(
61        "{:>10}  {:>14}  {:>4}  {:>18}",
62        "count", "total_bytes", "frm", "top frame"
63    );
64    for (rank, site) in sites.iter().take(10).enumerate() {
65        println!(
66            "{rank:>2}: {count:>6}  {bytes:>14}  {frm:>4}  {top:#018x}",
67            rank = rank,
68            count = site.count,
69            bytes = site.total_bytes,
70            frm = site.frame_count,
71            top = site.frames[0],
72        );
73    }
74}
Source

pub fn symbolicated_report(&self) -> Vec<SymbolicatedCallSite>

Available on crate feature symbolicate only.

Drain the per-call-site table and symbolicate each frame against the running binary’s own debug info.

Available only with the symbolicate cargo feature, which also implies backtraces. Returns one SymbolicatedCallSite per unique call site, each carrying resolved function names plus (where available) source file and line.

Allocates. Safe to call from non-allocator contexts only (ordinary user code outside the global-allocator hook).

Results are cached per-address across calls.

§Example
use mod_alloc::ModAlloc;

#[global_allocator]
static GLOBAL: ModAlloc = ModAlloc::new();

let _v: Vec<u8> = vec![0; 1024];
for site in GLOBAL.symbolicated_report() {
    let top = &site.frames[0];
    println!("{} allocs / {} bytes at {}",
        site.count,
        site.total_bytes,
        top.function.as_deref().unwrap_or("<unresolved>"));
}
Examples found in repository?
examples/symbolicate.rs (line 49)
38fn main() {
39    for _ in 0..500 {
40        alloc_small();
41    }
42    for _ in 0..100 {
43        alloc_medium();
44    }
45    for _ in 0..10 {
46        alloc_large();
47    }
48
49    let mut report = GLOBAL.symbolicated_report();
50    report.sort_by_key(|s| std::cmp::Reverse(s.total_bytes));
51
52    let snap = GLOBAL.snapshot();
53    println!("Process-wide:");
54    println!(
55        "  {} allocations, {} total bytes, peak {} bytes",
56        snap.alloc_count, snap.total_bytes, snap.peak_bytes
57    );
58    println!();
59
60    println!("Top call sites by total bytes:");
61    println!(
62        "{:>2}  {:>10}  {:>14}  top frame",
63        "#", "count", "total_bytes"
64    );
65    for (rank, site) in report.iter().take(10).enumerate() {
66        let top = &site.frames[0];
67        let name = top.function.as_deref().unwrap_or("<unresolved>");
68        let loc = match (top.file.as_ref(), top.line) {
69            (Some(f), Some(l)) => format!("  {}:{}", f.display(), l),
70            _ => String::new(),
71        };
72        println!(
73            "{rank:>2}  {count:>10}  {bytes:>14}  {name}{loc}",
74            rank = rank,
75            count = site.count,
76            bytes = site.total_bytes,
77            name = name,
78            loc = loc,
79        );
80        // Show inlined expansions if any.
81        for inlined in site.frames.iter().skip(1).take_while(|f| f.inlined) {
82            let n = inlined.function.as_deref().unwrap_or("<unresolved>");
83            println!("        inlined: {n}");
84        }
85    }
86}

Trait Implementations§

Source§

impl Default for ModAlloc

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl GlobalAlloc for ModAlloc

Source§

unsafe fn alloc(&self, layout: Layout) -> *mut u8

Allocates memory as described by the given layout. Read more
Source§

unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8

Behaves like alloc, but also ensures that the contents are set to zero before being returned. Read more
Source§

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout)

Deallocates the block of memory at the given ptr pointer with the given layout. Read more
Source§

unsafe fn realloc( &self, ptr: *mut u8, layout: Layout, new_size: usize, ) -> *mut u8

Shrinks or grows a block of memory to the given new_size in bytes. The block is described by the given ptr pointer and layout. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.