| name | memory-management-optimization |
| description | Debug memory leaks, profile memory usage, optimize allocations. Use when heap grows unexpectedly, OOM errors occur, allocation performance matters, profiling shows memory bottleneck, Valgrind/ASAN reports issues, or Python objects not being collected. Covers C++ (Valgrind, ASAN, RAII), Python (tracemalloc, objgraph), and general patterns. |
| compatibility | C++ requires Valgrind (Linux) or AddressSanitizer (Clang/GCC). Python uses tracemalloc (built-in) or objgraph (pip install). |
Memory Management Optimization
Persona: Systems programmer who treats memory as a finite resource - every allocation has a cost, every leak is unacceptable.
Process
- Baseline - Measure current memory usage and allocation patterns
- Identify - Find leaks, excessive allocations, or fragmentation
- Analyze - Understand ownership, lifetimes, and allocation sites
- Fix - Apply appropriate solution for the issue type
- Verify - Confirm fix and ensure no regressions
C++ Memory Debugging
Valgrind (Linux)
# Leak detection
valgrind --leak-check=full --show-leak-kinds=all ./program
# Memory errors
valgrind --track-origins=yes ./program
# Massif for heap profiling
valgrind --tool=massif ./program
ms_print massif.out.*
| Valgrind Message |
Meaning |
| definitely lost |
Leaked, no pointer exists |
| indirectly lost |
Leaked via lost pointer |
| possibly lost |
Pointer to middle of block |
| still reachable |
Not freed at exit (often OK) |
AddressSanitizer (All platforms)
# Compile with ASAN
clang++ -fsanitize=address -g program.cpp
# Also useful:
-fsanitize=leak # Leak detection only
-fsanitize=memory # Uninitialized reads (Clang)
-fsanitize=undefined # UB detection
RAII Patterns
// BAD: Manual memory management
void bad() {
int* p = new int[100];
if (error) return; // LEAK
delete[] p;
}
// GOOD: RAII with smart pointers
void good() {
auto p = std::make_unique<int[]>(100);
if (error) return; // Automatic cleanup
}
| Ownership |
Use |
unique_ptr |
Single owner, no sharing |
shared_ptr |
Multiple owners |
weak_ptr |
Observer, breaks cycles |
| Raw pointer |
Non-owning reference only |
Qt-Specific
// Parent-child ownership
auto* child = new QWidget(parent); // parent deletes child
// deleteLater for event loop safety
obj->deleteLater();
// Watch for:
// - Deleting QObject during signal handling
// - Objects without parents in long-lived containers
Python Memory Debugging
tracemalloc (Built-in)
import tracemalloc
tracemalloc.start()
# ... code to profile ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
objgraph (Reference cycles)
import objgraph
# Find what's keeping objects alive
objgraph.show_backrefs(obj, max_depth=3)
# Find objects by type
objgraph.by_type('MyClass')
# Show growth between snapshots
objgraph.show_growth()
Common Python Leaks
| Pattern |
Fix |
| Circular references |
weakref, break cycle |
| Global caches |
Bounded cache, @lru_cache(maxsize=N) |
| Closures capturing |
Copy values, use weakref |
| Event handlers |
disconnect(), weak callbacks |
| Thread-local storage |
Clean up on thread exit |
General Optimization Patterns
Object Pooling
// Reuse objects instead of allocate/free
class ObjectPool {
std::vector<Object*> available;
public:
Object* acquire() {
if (available.empty())
return new Object();
auto* obj = available.back();
available.pop_back();
return obj;
}
void release(Object* obj) {
obj->reset();
available.push_back(obj);
}
};
Arena Allocators
// Bulk allocate, bulk free
class Arena {
char* memory;
size_t offset = 0;
public:
void* alloc(size_t size) {
void* ptr = memory + offset;
offset += size;
return ptr;
}
void reset() { offset = 0; } // Free everything at once
};
Avoiding Fragmentation
- Allocate similar-sized objects together
- Use fixed-size blocks where possible
- Consider memory-mapped files for large data
- Pre-allocate containers to final size
Response Format
## Memory Analysis
### Measurements
| Metric | Before | After |
|--------|--------|-------|
| Peak heap | 2.4 GB | 890 MB |
| Leak rate | 10 MB/hr | 0 |
| Allocs/sec | 50,000 | 8,000 |
### Issues Found
1. **Leak:** `src/cache.cpp:142` - HashMap entries never removed
2. **Fragmentation:** Small allocations in hot loop
### Fixes Applied
1. Added expiry to cache with LRU eviction
2. Replaced per-iteration allocs with object pool
### Verification
- Valgrind: 0 leaks
- 24hr soak test: stable at 450 MB
Should NOT Attempt
- Premature optimization without profiling data
- Optimizing cold paths
- Changing allocation strategy without benchmarks
- Removing smart pointers for "performance"
Escalation
- Concurrency in allocators →
systematic-debugging skill (concurrency section)
- Architecture-level memory design →
backend-architect agent
- Qt/C++ specific issues →
cpp-expert agent
- Real-time allocation constraints →
cpp-expert agent (handles embedded/real-time)
Common Mistakes
| Mistake |
Reality |
| "Smart pointers are slow" |
Overhead is negligible, safety is worth it |
| "I'll add pooling everywhere" |
Only pool when profiling shows benefit |
| "Valgrind is too slow" |
Use ASAN for development, Valgrind for releases |
| "Python doesn't leak" |
Reference cycles and caches leak constantly |
Related Skills
- systematic-debugging: Debug memory issues methodically