Memory Analysis: Valgrind, heaptrack, memray, Heap Profiling
Introduction
Memory issues — leaks, fragmentation, excessive allocation, and buffer overflows — are among the hardest bugs to diagnose. Specialized memory analysis tools can pinpoint the exact line of code causing the problem. This article covers Valgrind for C/C++ memory errors, heaptrack for Linux heap profiling, memray for Python memory tracking, and general heap profiling techniques.
Valgrind
The gold standard for C/C++ memory error detection:
# Basic memory check
valgrind ./myapp
valgrind --leak-check=yes ./myapp
valgrind --tool=memcheck --leak-check=full ./myapp
# Detailed output
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./myapp
# Suppress known leaks
valgrind --suppressions=suppressions.txt ./myapp
# Generate suppression file
valgrind --leak-check=full --gen-suppressions=all ./myapp 2> suppressions.txt
# Cache profiling
valgrind --tool=cachegrind ./myapp
# Call graph profiling
valgrind --tool=callgrind ./myapp
# Massif (heap profiler)
valgrind --tool=massif ./myapp
ms_print massif.out.12345 # View heap profile
# Profile specific duration
valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes ./myapp
// Example of errors Valgrind catches
void memory_errors() {
// Buffer overflow — Valgrind catches this
char buf[10];
buf[10] = 'x'; // Error: invalid write
// Use-after-free
char *ptr = malloc(10);
free(ptr);
ptr[0] = 'a'; // Error: invalid read/write
// Memory leak
char *leak = malloc(100); // Never freed
// Valgrind: definitely lost: 100 bytes
// Uninitialized value
int x;
if (x == 5) {} // Error: conditional depends on uninit value
}
**Key tools**: `memcheck` for memory errors and leaks, `cachegrind` for cache profiling, `callgrind` for call graph analysis, `massif` for heap profiling.
heaptrack
A modern Linux heap memory profiler with lower overhead than Valgrind:
# Installation
sudo apt install heaptrack # Debian/Ubuntu
brew install heaptrack # macOS (partial)
# Profile an application
heaptrack ./myapp
heaptrack ./myapp arg1 arg2
# Attach to running process
heaptrack -p 12345
# Analyze results
heaptrack_print heaptrack.myapp.12345.gz
heaptrack_gui heaptrack.myapp.12345.gz # GUI viewer
# Output analysis
heaptrack_print --print-leak-types --print-total heaptrack.myapp.12345.gz
**Key features**: Lower overhead than Valgrind (suitable for production-like loads), call-stack-based allocation tracking, detailed time-based allocation charts, peak memory analysis, leak detection.
memray
Python memory profiler with high-resolution tracking:
# Installation
pip install memray
# Profile a script
memray run myapp.py
memray run -o output.bin myapp.py
# Profile with live tracking
memray run --live myapp.py
# Attach to running process
memray attach --pid 12345 --output output.bin
# Generate reports
memray flamegraph output.bin # Interactive flamegraph
memray table output.bin # Text table report
memray tree output.bin # Tree view
memray stats output.bin # Summary statistics
memray summary output.bin # High-level summary
# Compare allocations
memray diff before.bin after.bin
# Python native support
# Programmatic memory tracking
import memray
with memray.Tracker("profile.bin"):
# Code to profile
data = [i for i in range(1000000)]
processed = [x * 2 for x in data]
# Decorator for function profiling
@memray.tracker("func_profile.bin")
def my_function():
pass
# Context manager with specific recording
with memray.Tracker("allocations.bin", native_traces=True):
large_list = [object() for _ in range(500000)]
**Key features**: Python-native (no C extension needed in many cases), thread-safe, native stack traces, live tracking, multiple report formats including interactive flamegraphs.
General Heap Profiling
jemalloc heap profiling
# Enable jemalloc profiling
export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:17"
./myapp
# Trigger profile dump
kill -SIGUSR2 $PID # Dumps heap profile
jeprof --show_bytes --pdf ./myapp heap.prof > heap.pdf
# Compare profiles
jeprof --show_bytes --pdf ./myapp --base=heap1.prof heap2.prof > diff.pdf
GCC address sanitizer
# Compile with address sanitizer
gcc -fsanitize=address -g -O1 myapp.c -o myapp
./myapp # Will detect buffer overflows and use-after-free
# Leak sanitizer
gcc -fsanitize=leak -g myapp.c -o myapp
Memory Analysis Workflow
# 1. Detect the issue (OOM or high RSS growth)
# 2. Run with memcheck for memory errors
valgrind --leak-check=full ./myapp
# 3. Profile heap with massif or heaptrack
heaptrack ./myapp
# 4. Analyze the profile
heaptrack_print heaptrack.*.gz
# 5. For Python: use memray
memray run myapp.py
memray flamegraph memray-myapp.*.bin
Comparison
| Tool | Language | Overhead | Best For |
|------|----------|----------|----------|
| Valgrind | C/C++ | 10-20x | Memory errors, leaks |
| heaptrack | C/C++/Rust | 2-3x | Heap allocation profiling |
| memray | Python | 1.5-3x | Python memory tracking |
| jemalloc | C/C++ | Low | Production heap profiling |
| ASan | C/C++ | 2x | Buffer overflows, UAF |
Recommendations
* **C/C++ memory errors**: Valgrind memcheck is the definitive tool for finding use-after-free, buffer overflows, and uninitialized values.
* **C/C++ heap profiling**: heaptrack for lower overhead than Valgrind, suitable for larger applications.
* **Python memory analysis**: memray for high-resolution tracking and flamegraph visualization.
* **Production C/C++**: jemalloc profiling for continuous heap monitoring with minimal overhead.
* **Quick memory error detection**: GCC/Clang address sanitizer during development.
Start with the lowest-overhead tool for your language. For C/C++, use ASan during development and heaptrack for deeper analysis. For Python, memray is the best all-around choice. Reserve Valgrind for hard-to-find memory errors that simpler tools miss.