# How to Tune DART Simulation Performance: Optimization Best Practices for dartsim/dart

> Optimize DART simulation performance by profiling key layers like collision detection and dynamics integration. Learn best practices for faster solvers, SIMD builds, and constraint group splitting.

- Repository: [DART: Dynamic Animation and Robotics Toolkit/dart](https://github.com/dartsim/dart)
- Tags: best-practices
- Published: 2026-02-28

---

**The most effective way to tune DART simulation performance is to profile the dominant computational layer—collision detection, dynamics integration, or LCP constraint solving—and apply targeted optimizations such as selecting faster iterative solvers, enabling SIMD builds, and splitting independent constraint groups.**

Tuning DART simulation performance requires understanding its layered architecture that separates foundation math, collision detection, rigid body dynamics, and constraint solving. According to the dartsim/dart source code, each layer has distinct computational complexity characteristics that dictate where optimization efforts yield the greatest speedup.

## Understanding DART's Computational Architecture

DART's performance is determined by five distinct layers, each with specific complexity profiles documented in [`docs/onboarding/architecture.md`](https://github.com/dartsim/dart/blob/main/docs/onboarding/architecture.md)【L81-L95】:

| Layer | Computational Complexity | Primary Cost Driver |
|-------|-------------------------|-------------------|
| **Foundation** (math utilities) | O(n) | Vector operations, SIMD utilization |
| **Collision Detection** | O(m log m) broad-phase, O(p) narrow-phase | Backend choice, pair filtering |
| **Dynamics** (ABA algorithm) | O(n) | Tree depth, joint degrees of freedom |
| **Constraints** (LCP solve) | O(c³) | Number of active constraints |
| **Integration** | O(n) | Time-step size, semi-implicit Euler stability |

The **Constraint** layer typically dominates runtime in contact-rich simulations due to its cubic complexity, while the **Dynamics** layer scales linearly with degrees of freedom. Targeting the correct layer based on your scenario is essential before applying any performance knobs.

## Essential Tuning Strategies

### Time Step and Integration Settings

Keep the **time step (`dt`) modest**—the default is `0.001` seconds (1 kHz). Larger time steps force the semi-implicit Euler integrator to work harder to maintain stability, often requiring additional constraint iterations that compound the O(c³) cost【L104-L106】. 

Disable unnecessary sensors and trajectory recorders if real-time visualization is not required. Each additional sensor adds per-step overhead that bypasses the core physics optimizations.

### Collision Detection Optimization

Use the **FCL backend** (default) with **mesh mode** enabled for accurate but fast contact generation【L60-L66】. Enable **broad-phase filtering** via `CollisionFilter` to prune collision pairs that cannot interact, reducing narrow-phase checks from O(m²) to O(m log m)【L54-L55】.

For static environments, pre-compute Bounding Volume Hierarchies (BVH) and reuse them across frames to minimize redundant narrow-phase calculations.

### Constraint Solver Configuration

The **LCP (Linear Complementarity Problem)** solver choice significantly impacts performance. The default **DantzigSolver** provides robust convergence but executes slower iterative steps. For scenarios with many contacts, switch to the **PgsSolver** (Projected Gauss-Seidel), which offers faster convergence at the cost of reduced accuracy【L41-L43】.

Split independent constraint systems using **ConstrainedGroup** objects. DART solves each group separately, effectively reducing the `c` variable in the O(c³) complexity calculation【L70-L73】. Disable soft-contact constraints and unnecessary joint limit enforcement when not required.

### SIMD and Memory Optimization

Build DART with **SIMD enabled** targeting your hardware's instruction set (AVX2, AVX-512, or ARM NEON). Ensure Eigen is compiled with matching flags to maximize vectorized throughput in the foundation math layer【L45-L53】.

Use **pool allocators** instead of heap allocation for per-step temporary memory. DART implements this pattern in [`dart/common/PoolAllocator.hpp`](https://github.com/dartsim/dart/blob/main/dart/common/PoolAllocator.hpp)—custom simulation code should allocate from `PoolAllocator` or `FrameAllocator` to avoid heap churn【L76-L81】.

Enable **lazy evaluation** and version tracking for mass-matrix caching. DART only recomputes the mass matrix when state changes, avoiding repeated O(n²) builds during static contact phases【L98-L104】.

## Implementing Performance Tuning in Code

### Selecting a Faster LCP Solver

Replace the default Dantzig solver with PGS for speed-critical applications:

```cpp
#include <dart/dart.hpp>

int main() {
  auto world = dart::simulation::World::create();
  world->setTimeStep(0.001);
  world->setGravity(Eigen::Vector3d(0, 0, -9.81));

  // Load robot and add to world
  auto robot = dart::io::loadSkeleton("robot.urdf");
  world->addSkeleton(robot);

  // Switch to Projected Gauss-Seidel for faster constraint solving
  world->getConstraintSolver()->setLcpSolver(
      dart::math::PgsSolver::create());

  for (int i = 0; i < 1000; ++i) {
    world->step();
  }
}

```

This change targets the O(c³) constraint layer bottleneck identified in [`docs/onboarding/architecture.md`](https://github.com/dartsim/dart/blob/main/docs/onboarding/architecture.md)【L41-L44】.

### Enabling SIMD and Pool Allocation

Configure the build with native SIMD flags:

```bash
pixi run build -- -DCMAKE_CXX_FLAGS="-march=native -msse4.2 -mavx2"

```

Utilize pool allocators in custom simulation loops to eliminate heap fragmentation:

```cpp
#include <dart/common/PoolAllocator.hpp>
#include <dart/simd/Vector.hpp>

void simulateStep(dart::simulation::WorldPtr world) {
  dart::common::PoolAllocator pool;
  
  // Allocate SIMD vectors from pool (zero heap allocation)
  auto temp = pool.allocate<dart::simd::Vector<double, 4>>();
  temp = world->getSkeleton(0)->getVelocities().head<4>();

  // Process with SIMD-backed math
  auto result = dart::simd::transformPoint(temp, Eigen::Isometry3d::Identity());
  
  world->step();
}

```

The SIMD module is defined in [`dart/simd/All.hpp`](https://github.com/dartsim/dart/blob/main/dart/simd/All.hpp) and requires matching Eigen compilation flags【L45-L53】【L76-L81】.

### Optimizing Constraint Groups

Force parallel solving of independent constraint groups:

```cpp
auto solver = world->getConstraintSolver();

// Recompute groups after adding bodies or changing topology
solver->clearConstrainedGroups();
solver->generateConstrainedGroups();

// Enable parallel solving across CPU cores
solver->setParallel(true);

```

This optimization is referenced in the constraint layer documentation【L70-L73】.

## Advanced Performance Techniques

**Joint Type Selection:** Prefer **revolute** or **prismatic** joints over high-DOF joints like `FreeJoint` when possible. Fewer degrees of freedom directly reduce the O(n) cost of the Articulated Body Algorithm (ABA) dynamics pass.

**Contact Softening:** Apply **ERP** (Error Reduction Parameter) and **CFM** (Constraint Force Mixing) to relax stiff contacts. Softer constraints converge faster in the LCP solver, allowing larger time steps without stability loss.

**Memory Alignment:** Ensure data structures align to cache-line sizes (64 bytes on x86_64). DART handles this automatically via `Eigen::aligned_allocator`, but custom data structures should follow suit to prevent SIMD throughput degradation.

**Profiling Workflow:** Run the built-in SIMD benchmarks with `pixi run bm-simd` and analyze LCP performance using [`scripts/lcp_performance_profile.py`](https://github.com/dartsim/dart/blob/main/scripts/lcp_performance_profile.py) to identify actual bottlenecks before optimizing【L119-L124】.

## Summary

- **Profile first** using DART's built-in benchmarks (`bm-simd`, [`lcp_performance_profile.py`](https://github.com/dartsim/dart/blob/main/lcp_performance_profile.py)) to identify whether collision, dynamics, or constraints dominate your simulation.
- **Target the O(c³) constraint layer** by switching to `PgsSolver`, splitting independent groups with `ConstrainedGroup`, and disabling unnecessary constraints.
- **Optimize collision** with FCL backend broad-phase filtering and BVH caching for static meshes.
- **Enable SIMD builds** with matching Eigen flags and use `PoolAllocator` to eliminate per-step heap allocations.
- **Maintain modest time steps** (0.001s default) to keep the semi-implicit Euler integrator stable without excessive constraint iterations.

## Frequently Asked Questions

### How do I know which DART layer is slowing down my simulation?

Run the profiling scripts included in the repository: `pixi run bm-simd` benchmarks vectorized math operations, while [`scripts/lcp_performance_profile.py`](https://github.com/dartsim/dart/blob/main/scripts/lcp_performance_profile.py) analyzes constraint solver performance. If collision detection dominates, optimize broad-phase filtering; if LCP solving consumes time, switch to `PgsSolver` or reduce constraint counts.

### Is the DantzigSolver or PgsSolver better for real-time applications?

**PgsSolver** (Projected Gauss-Seidel) is better for real-time applications requiring high frame rates. While **DantzigSolver** offers superior accuracy for stiff constraints and complex contact scenarios, it executes slower iterative steps that may exceed time budgets in O(c³) systems with many contacts.

### Does DART automatically use multiple CPU cores?

DART supports parallel constraint solving when explicitly enabled. Call `ConstraintSolver::setParallel(true)` after generating constrained groups with `generateConstrainedGroups()`. Independent groups are then solved across available cores, scaling performance with CPU count for systems with separable constraint islands.

### What time step should I use for stable DART simulations?

The default **0.001 seconds** (1 kHz) provides a good balance between stability and performance. Larger steps risk integration error accumulation in the semi-implicit Euler integrator, potentially requiring additional constraint iterations that compound the cubic cost of LCP solving. Reduce the step size if you observe instability in stiff systems.