Memory Profiling User Guide
Overview
Memory profiling is an optional feature in PyBulletFleet that tracks memory usage during simulation. This is particularly useful for:
Detecting memory leaks in long-running simulations
Monitoring memory consumption in multi-robot scenarios
Optimizing memory usage by identifying memory-intensive operations
Continuous Integration (CI) testing to prevent memory regressions
Memory profiling uses Python’s built-in tracemalloc module, so no external dependencies are required.
Quick Start
1. Enable Memory Profiling in Configuration
Add enable_memory_profiling: true to your YAML configuration file:
# config.yaml
enable_time_profiling: true # Time profiling (optional, independent)
enable_memory_profiling: true # Memory profiling
profiling_interval: 100 # Report every 100 steps
2. Run Your Simulation
from pybullet_fleet.core_simulation import MultiRobotSimulationCore
# Load configuration with memory profiling enabled
sim = MultiRobotSimulationCore.from_config("config.yaml")
# Run simulation
sim.run_simulation(duration=60.0) # 60 seconds simulation time
3. Check Memory Usage Output
Memory statistics will be printed every profiling_interval steps:
[MEMORY] Last 100 steps: current=245.32MB (min=243.18, max=246.57), peak=248.45MB (max=250.12), growth=+3.25MB
API Reference
Enable/Disable Memory Profiling
Configuration File:
enable_memory_profiling: true # Enable memory profiling
profiling_interval: 100 # Report interval (steps)
Python API:
sim = MultiRobotSimulationCore(
enable_memory_profiling=True,
profiling_interval=100
)
Get Current Memory Usage
get_memory_usage() can be called at any time while tracemalloc is active —
including inside a callback during run_simulation():
# Inside a callback: monitor memory every N steps
def memory_monitor(sim_core, dt):
mem = sim_core.get_memory_usage()
if mem:
print(f"Heap: {mem['current_mb']:.2f} MB, Peak: {mem['peak_mb']:.2f} MB")
sim.register_callback(memory_monitor, frequency=10)
sim.run_simulation()
You can also call it after the simulation ends to get the final snapshot:
mem_usage = sim.get_memory_usage()
if mem_usage:
print(f"Current: {mem_usage['current_mb']:.2f} MB")
print(f"Peak: {mem_usage['peak_mb']:.2f} MB")
Return Value:
Noneif memory profiling is not enabledDictionary with keys:
current_mb: Current memory usage in MBpeak_mb: Peak memory usage since profiling started
Understanding Memory Statistics
Memory Report Fields
[MEMORY] Last 100 steps: current=245.32MB (min=243.18, max=246.57), peak=248.45MB (max=250.12), growth=+3.25MB
current: Average current memory usage over last N steps
min: Minimum current memory in the interval
max: Maximum current memory in the interval
peak: Average peak memory (highest ever reached) over last N steps
max: Maximum peak memory in the interval
growth: Memory increase from first to last sample in interval
Positive value indicates memory growth (potential leak)
Negative value indicates memory release
Zero or near-zero indicates stable memory usage
Interpreting Growth Values
Growth Value |
Interpretation |
|---|---|
|
Normal for initialization or object spawning |
|
Monitor closely, may indicate inefficiency |
|
Likely memory leak, investigate immediately |
|
Healthy, stable memory usage |
Consistently positive |
Potential memory leak |
Use Case: Detecting Memory Leaks in Long-Running Simulations
config = {
"enable_memory_profiling": True,
"profiling_interval": 500, # Report every 500 steps
}
sim = MultiRobotSimulationCore.from_config(config)
# ... spawn agents ...
sim.run_simulation() # Ctrl+C or GUI close returns normally
# Check final memory usage
final_mem = sim.get_memory_usage()
if final_mem:
print(f"Final memory: {final_mem['current_mb']:.2f} MB")
During the run, watch the growth field in the periodic reports:
[MEMORY] Last 500 steps: current=250.45MB (...), growth=+0.12MB # stable ✓
[MEMORY] Last 500 steps: current=250.58MB (...), growth=+0.15MB # stable ✓
[MEMORY] Last 500 steps: current=251.02MB (...), growth=+5.45MB # investigate!
For CI, assert a ceiling in a test:
def test_memory_usage_within_limits():
"""Ensure simulation memory stays below threshold."""
sim = MultiRobotSimulationCore(
enable_memory_profiling=True,
)
# ... spawn agents, run ...
mem = sim.get_memory_usage()
assert mem and mem["current_mb"] < 500.0, "Memory usage exceeds 500 MB limit"
Best Practices
✅ DO
Enable memory profiling for long-running simulations (>1 minute)
Monitor growth values to detect potential leaks
Set profiling_interval based on simulation length:
Short simulations (< 1 min):
profiling_interval: 50Medium simulations (1-10 min):
profiling_interval: 100Long simulations (> 10 min):
profiling_interval: 500
Add memory assertions to tests:
assert mem["current_mb"] < MAX_MEMORY_MB
Profile before and after code changes to detect regressions
❌ DON’T
Don’t enable memory profiling in production (slight performance overhead)
Don’t panic over small growth values (< 2MB per 100 steps is normal)
Don’t rely only on memory profiling - use time profiling together
Don’t forget to close simulation after profiling
Performance Impact
Memory profiling uses Python’s tracemalloc module, which has minimal overhead:
CPU overhead: < 1% in most cases
Memory overhead: ~5-10% additional memory for tracking
Recommended for: Development, testing, debugging
Not recommended for: Production deployments requiring maximum performance
Troubleshooting
Memory profiling returns None
Problem:
mem = sim.get_memory_usage()
print(mem) # None
Solution:
Ensure enable_memory_profiling: true in configuration:
enable_memory_profiling: true
No memory statistics printed
Problem:
Memory profiling enabled but no [MEMORY] logs appear.
Solution:
Check
profiling_intervalsetting (default: 100 steps)Run enough steps to trigger report:
sim.step_once()× profiling_intervalEnsure logging level allows INFO messages
Memory keeps growing
Problem:
[MEMORY] Last 100 steps: growth=+5.2MB
[MEMORY] Last 100 steps: growth=+4.8MB
[MEMORY] Last 100 steps: growth=+5.1MB # Continuously positive!
Solution:
Check for memory leak candidates:
# Bad: List comprehension creates new list every frame self._data = [x for x in self._data if condition(x)] # Good: In-place removal while self._data and not condition(self._data[0]): self._data.pop(0)
Use external profilers for detailed analysis:
pip install memory_profiler mprof run python examples/your_demo.py mprof plot
Example Output
Normal Memory Usage (Healthy)
[MEMORY] Last 100 steps: current=245.32MB (min=243.18, max=246.57), peak=248.45MB (max=250.12), growth=+0.15MB
[MEMORY] Last 100 steps: current=245.48MB (min=244.32, max=246.89), peak=248.45MB (max=250.12), growth=+0.12MB
[MEMORY] Last 100 steps: current=245.55MB (min=244.89, max=246.34), peak=248.45MB (max=250.12), growth=-0.05MB
Interpretation: Stable memory usage (~245MB), minimal growth, healthy simulation.
Memory Leak Detected (Unhealthy)
[MEMORY] Last 100 steps: current=245.32MB (min=243.18, max=246.57), peak=248.45MB (max=250.12), growth=+5.23MB
[MEMORY] Last 100 steps: current=250.55MB (min=248.92, max=252.14), peak=253.67MB (max=255.34), growth=+4.87MB
[MEMORY] Last 100 steps: current=255.42MB (min=253.78, max=257.09), peak=258.54MB (max=260.21), growth=+5.12MB
Interpretation: Continuous memory growth (+5MB per interval), potential memory leak, requires investigation.
References
Real-time Synchronization Design - Real-time synchronization design
Collision Detection Overview - Collision detection system
Profiling Guide — Benchmark scripts for CPU time analysis (
simulation_profiler.py,wrapper_overhead.py, etc.)