Time Profiling User Guide
Overview
Time profiling measures per-step execution time of each simulation component inside step_once().
It helps identify performance bottlenecks by breaking down where time is spent every simulation step.
Component |
What it measures |
|---|---|
|
All |
|
User-registered callbacks via |
|
|
|
Spatial-hash broad phase + PyBullet narrow phase |
|
|
|
End-to-end step time |
Overhead: < 0.1% CPU — safe to leave enabled during development.
Quick Start
1. Enable Time Profiling in Configuration
# config.yaml
enable_time_profiling: true # Enable step-timing reports
profiling_interval: 100 # Print average every 100 steps
log_level: "info" # Required: reports use logger.info()
2. Run Your Simulation
from pybullet_fleet.core_simulation import SimulationParams, MultiRobotSimulationCore
params = SimulationParams.from_config("config/config.yaml")
# Or directly:
params = SimulationParams(
enable_time_profiling=True,
profiling_interval=100,
)
sim = MultiRobotSimulationCore(params)
3. Read the Output
Every profiling_interval steps, a summary line is logged:
[PROFILING] Last 100 steps average: agent_update=0.45ms (35.2%), callbacks=0.02ms (1.6%),
step_simulation=0.00ms (0.0%), collision_check=0.75ms (58.6%), monitor_update=0.06ms (4.7%),
total=1.28ms (100.0%)
Each component shows average time (ms) and percentage of total.
Configuration Reference
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
bool |
|
Enable per-step timing measurement |
|
int |
|
Steps between summary reports |
Both parameters can be set via YAML config or SimulationParams constructor.
profiling_interval is shared with memory profiling — it controls the reporting
interval for both systems.
Interpreting Results
Healthy Profile (kinematics mode, 100 robots)
[PROFILING] Last 100 steps average: agent_update=0.45ms (35.2%), collision_check=0.75ms (58.6%), total=1.28ms
collision_checkdominates — normal for large scenesstep_simulation≈ 0 — expected in physics-off modeTotal < 2ms per step — good performance
Bottleneck: Collision Detection
[PROFILING] Last 100 steps average: agent_update=0.50ms (10.0%), collision_check=4.50ms (90.0%), total=5.00ms
collision_check> 80% → optimize spatial hash cell size or reduce collision-enabled objectsSee Collision Configuration for tuning
Bottleneck: Agent Update
[PROFILING] Last 100 steps average: agent_update=3.20ms (80.0%), collision_check=0.75ms (18.8%), total=4.00ms
agent_update> 70% → check action complexity, path length, or number of attached objects
Bottleneck: Physics Step
[PROFILING] Last 100 steps average: step_simulation=8.00ms (72.7%), collision_check=2.50ms (22.7%), total=11.00ms
step_simulationdominates → expected in physics-on modeConsider switching to kinematics mode if physics fidelity is not required
Programmatic Access
Use return_profiling=True on step_once() to get timing data as a dictionary
instead of logging:
for _ in range(1000):
timings = sim.step_once(return_profiling=True)
if timings:
print(f"Agent update: {timings['phase1_update']:.2f}ms")
print(f"Collision: {timings['collision_check']:.2f}ms")
print(f"Total: {timings['total']:.2f}ms")
Return value fields
All times are in milliseconds.
Key |
What it measures |
|---|---|
|
Full Phase 1: agent.update + callbacks + plugin on_step (buffered writes) |
|
Phase 2: flush buffered poses to PyBullet |
|
Phase 3: kinematic AABB refresh + spatial-grid update |
|
Per-object update loop only (subset of |
|
User-registered callbacks via |
|
|
|
Broad phase (spatial hash) + narrow phase total |
|
Nested dict — present only when collision ran (see below) |
|
EventBus |
|
EventBus |
|
EventBus collision-pair dispatch |
|
DataMonitor overlay refresh |
|
End-to-end step time |
collision_breakdown sub-fields:
Key |
What it measures |
|---|---|
|
Fetch AABB bounds from PyBullet |
|
Rebuild spatial hash buckets |
|
Broad-phase candidate pair filtering |
|
Narrow-phase contact query ( |
|
Sum of collision sub-phases |
Example: per-step CSV logging
import csv, sys
writer = csv.DictWriter(sys.stdout,
fieldnames=["step", "phase1_update", "collision_check", "total"])
writer.writeheader()
for i in range(500):
t = sim.step_once(return_profiling=True)
if t:
writer.writerow({"step": i,
"phase1_update": round(t["phase1_update"], 3),
"collision_check": round(t["collision_check"], 3),
"total": round(t["total"], 3)})
This is useful for custom analysis, plotting, or CI performance regression tests.
Best Practices
Always enable during development — overhead is negligible (< 0.1%)
Disable for production — avoids log noise
Use
profiling_interval=100for stable averages; lower values (10-50) for debugging spikesCombine with memory profiling for comprehensive analysis:
enable_time_profiling: true enable_memory_profiling: true profiling_interval: 100
Use
return_profiling=Truefor automated benchmarks instead of parsing logs
Troubleshooting
No profiling output appears
Cause: enable_time_profiling is not enabled or log level is too high.
Solution:
params = SimulationParams(
enable_time_profiling=True,
profiling_interval=100,
log_level="info", # Must be info or lower
)
Or in YAML:
enable_time_profiling: true
profiling_interval: 100
log_level: "info"
Output appears too frequently / infrequently
Cause: profiling_interval does not match your needs.
Solution: Adjust profiling_interval:
10-50: Fine-grained debugging100: Normal development (default)500-1000: Long-running simulations
Benchmark scripts
For repeatable, process-isolated measurement use the scripts under benchmark/:
Script |
Purpose |
|---|---|
|
Batch vs per-agent step-time comparison with phase breakdown. Runs a fixed number of steps (not wall-clock duration) for stable statistics. |
|
Multi-run sweep with subprocess isolation and median/stdev. Supports |
|
Worker process called by |
Batch vs per-agent comparison (requires ≥ 300 steps for stable results — use --duration 30 at the default 0.1 s timestep):
# Quick comparison in one process (phase breakdown shown)
python3 benchmark/batch_perf.py --n 1000 --mode omni
# Rigorous comparison with process isolation across agent counts
python3 benchmark/run_benchmark.py --compare per_agent batch_omni \
--sweep 100 500 1000 2000 --duration 30 --repetitions 3
Scenario names (per_agent, batch_omni, etc.) are defined under the scenarios:
key in benchmark/configs/general.yaml. Use --config to point at a custom file.
Note: With
timestep=0.1andduration=5, only 50 steps are measured — GC pauses can inflate the mean by 10× or more. Use--duration 30(300 steps) or longer for reliable numbers.
References
Memory Profiling User Guide — Memory usage tracking
Collision Configuration — Collision settings and cell size tuning