Memory Usage Analysis

Profile Memory and Track Allocations

  • While not Rust-specific, valgrind (with massif or memcheck) is a very common and powerful memory profiler. You'd run your Rust program under Valgrind.
  • heaptrack is a heap profiler that can track memory allocations.

Profile Heap Memory with dhat

dhat dhat-crates.io dhat-github dhat-lib.rs

dhat is a library for heap profiling and ad-hoc profiling with DHAT.


// `dhat` provides heap profiling and ad-hoc profiling capabilities to Rust,
// similar to those provided by `DHAT`, a powerful heap profiler that
// comes with `Valgrind`. Warning: This crate is experimental.

// First, add to your `Cargo.toml`:
// [dependencies]
// dhat = "0.3.3"
//
// # You should only use `dhat` in release builds.
// [profile.release]
// debug = 1
//
// # Create features that lets you easily switch profiling on and off:
// [features]
// dhat-heap = []    # if you are doing heap profiling
// dhat-ad-hoc = []  # if you are doing ad hoc profiling

// The heap profiling works by using a global allocator that wraps the system
// allocator and tracks all heap allocations.
// We set `dhat::Alloc` as the global allocator
// using the #[global_allocator] attribute.
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;

// When you run the code below with:
// cargo run --features dhat-heap
// `dhat` will collect heap profiling information.
// The profiling data will be saved in a file named `dhat-heap.json`
// in the current directory. You can then use e.g.
// https://nnethercote.github.io/dh_view/dh_view.html
// to analyze the profiling data.
// The profiler also prints to `stderr`, like:
// dhat: Total:     9,200 bytes in 10 blocks (note: "block" = allocation)
// dhat: At t-gmax: 5,120 bytes in 2 blocks
// dhat: At t-end:  1,024 bytes in 1 blocks
fn heap_profiling() {
    // Start the heap profiling session
    #[cfg(feature = "dhat-heap")]
    let _profiler = dhat::Profiler::new_heap();

    // Example code to profile:
    // Create a vector and pushes 1000 elements into it.
    let mut vec = Vec::new();
    for i in 0..1000 {
        vec.push(i);
    }
    #[cfg(feature = "dhat-heap")]
    println!("Memory profiling complete!");
}

// Ad hoc profiling involves manually annotating hot code points.
// Run with: cargo run --features dhat-ad-hoc
// The profiler prints to `stderr`:
// dhat: Total:     100,000 units in 100,000 events
// dhat: The data has been saved to dhat-ad-hoc.json, and is viewable with
// dhat/dh_view.html
fn adhoc_profiling() {
    #[cfg(feature = "dhat-ad-hoc")]
    let _profiler = dhat::Profiler::new_ad_hoc();

    fn hot_function() {
        #[cfg(feature = "dhat-ad-hoc")]
        dhat::ad_hoc_event(1);
    }

    for _ in 0..100000 {
        hot_function();
    }
}

pub fn main() {
    heap_profiling();
    adhoc_profiling();
}

// `dhat` also supports heap usage testing, where you can write tests and then
// check that they allocated as much heap memory as you expected.
#[test]
fn heap_usage_testing() {
    // `testing()` allows the use of dhat::assert! and related macros,
    // and disables saving of profile data on Profiler drop.
    let _profiler = dhat::Profiler::builder().testing().build();

    let _v1: Vec<i32> = vec![1, 2, 3, 4];
    let v2: Vec<u8> = vec![5, 6, 7, 8];
    drop(v2);

    let stats = dhat::HeapStats::get();

    // Total allocations and number of bytes:
    // dhat::assert_eq!(stats.total_blocks, 2);
    dhat::assert_eq!(stats.total_bytes, 20);

    // Allocations and number of bytes at the point of peak heap size:
    // dhat::assert_eq!(stats.max_blocks, 2);
    dhat::assert_eq!(stats.max_bytes, 20);

    // Now a single allocation remains alive.
    // dhat::assert_eq!(stats.curr_blocks, 1);
    dhat::assert_eq!(stats.curr_bytes, 16);
}
// Example adapted from https://docs.rs/dhat/latest/dhat/

Leak Detection

  • valgrind (with memcheck) is excellent for detecting memory leaks.
  • Address Sanitizer (ASan) can detect memory leaks and other memory errors. Enable it with compiler flags (e.g., -fsanitize=address).

Examine Memory with Debugging Tools

gdb, lldb debuggers can be used to inspect memory, examine variables, and track allocations.

Use Tracing to Understand Allocation Patterns

tracing can help you understand the flow of your program and identify areas where excessive allocations might be occurring. See Tracing.

Execution Time Measurement

measure_time can help you measure the execution time of code blocks, which can be useful when investigating memory-related performance issues.