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-debuggingskill (concurrency section) - Architecture-level memory design →
backend-architectagent - Qt/C++ specific issues →
cpp-expertagent - Real-time allocation constraints →
cpp-expertagent (handles embedded/real-time)
When Blocked
If memory debugging stalls:
- Ensure profiling tools are properly installed (Valgrind, ASAN, tracemalloc)
- Verify debug symbols are present (build with -g)
- Try alternative tool (ASAN if Valgrind too slow, tracemalloc for Python)
- For stubborn leaks, add manual logging around suspected allocations
- Report specific tool output and what's been tried
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
- cpp-expert: C++ memory management, RAII, smart pointers
- qt-qml-expert: Qt object ownership, parent-child memory model