Optimize Cycle Time for Repetitive Tasks
Repetitive applications — pick-and-place, sorting, kitting, inspection — run the same sequence of work thousands of times per shift. Cycle time is the wall-clock duration of one iteration of that sequence. Reducing it lifts throughput for the rest of the deployment's life: trimming 5 seconds off a 25-second cycle pays back on every pick.
This guide covers how to measure cycle time in a MoveIt Pro Objective, how to interpret the measurement, and a catalog of patterns that consistently improve it. It assumes you are comfortable authoring Behavior Trees and MoveIt Task Constructor (MTC) tasks; if you are not, start with the Pick and Place Using MTC tutorial.
Engineers tuning an existing Objective that already works. If you have not yet validated that your Objective is functionally correct, do that first — optimizing a fragile sequence is wasted effort.
The Motion Ceiling
Every cycle breaks into two kinds of time:
- Execution — the arm actually moving along a trajectory. This time is bounded below by your motion limits and the path you chose; you cannot go below it without changing the path or the limits.
- Everything else — planning, perception, planning-scene updates, controller switches, Blackboard operations, waits. This is overhead.
Add up the execution time from one iteration. That sum is the motion ceiling — the theoretical minimum the cycle can reach with its current path. The difference between total cycle time and the ceiling is your optimization budget. Every pattern in this guide is a way to shrink that gap.
Categorize timed work into six kinds so that a measurement log tells you what is expensive, not just that something is:
| Category | What it covers |
|---|---|
| Execution | Trajectory execution (ExecuteMTCSolution, FollowJointTrajectory) |
| Planning | Trajectory planning (PlanMTCTask, SetupMTCPlanToPose, IK) |
| Perception | Cameras, point clouds, object registration |
| Scene | Planning-scene mutations — objects, attachments, ACM |
| Control | ros2_control activations, controller switches |
| Blackboard | Pure BT state ops (pose construction, MTC task setup) |
Knowing a slow Subtree's category points you directly at the applicable optimization.
Measure First
Do not optimize before you have measured.
Wrap Work With StopwatchBegin and StopwatchEnd
StopwatchBegin records a timepoint; StopwatchEnd computes the elapsed time and emits it to the log. Wrap any sequence you want to time:
<Action ID="StopwatchBegin" timepoint="{t_pick}" />
<SubTree ID="Vacuum Pick Bracket Part Subtree" _collapsed="true" />
<Action ID="StopwatchEnd" timepoint="{t_pick}" message="EXECUTION: pick" />
StopwatchEnd writes two lines to the Objective log: a canonical Time elapsed: X.XXXs line plus the optional message. Both are grep-friendly.
Use a Category Prefix in Every Message
Prefix the message port with the category in ALL CAPS followed by a colon — EXECUTION:, PLANNING:, PERCEPTION:, SCENE:, CONTROL:, BLACKBOARD:. A single consistent convention makes it trivial to slice a log by category or to compute the motion ceiling with one grep.
Factor each timed sequence into its own named Subtree so the Stopwatch* wrappers don't clutter the parent tree. It also lets you enable or disable timing for a section by swapping one SubTree reference.
Run the Objective With Verbose Output
Verbose mode streams all publishInfoMessage calls — including both StopwatchEnd lines — to stdout.
moveit_pro run -v | grep StopwatchEnd
You will see pairs of lines per timed section:
[StopwatchEnd] Time elapsed: 2.134s
[StopwatchEnd] EXECUTION: pick
Run the Objective long enough to get at least 10 steady-state iterations. Discard the first one — it usually carries cold-start overhead from container startup, first-time plan cache misses, and initial scene loads.
Visualize With a Gantt Chart
The prefixed log is parseable into a Gantt chart: one bar per timed section, stacked by iteration, colored by category. A Gantt view makes it immediately obvious which bars dominate and which items run sequentially when they could overlap. Even a sorted text view of the EXECUTION: / PLANNING: / PERCEPTION: lines is usually enough to spot the largest contributors.
Find the Gap
Sum the EXECUTION: bars from the fastest iteration — that is your ceiling. Compare it to the total cycle time:
- Within 10% of ceiling: stop. You are near the motion floor. Further gains require changing the path, the motion limits, or the hardware.
- Gap is significant: identify the single largest non-
EXECUTIONbar. Ask three questions in order:- Can this run in parallel with motion?
- Does it need to run on every iteration, or can it be hoisted out of the loop?
- Is there a faster Behavior for the same result?
The patterns below are the concrete answers.
Optimization Patterns
Precompute Trajectories Between Common Poses
A repetitive Objective typically cycles through a handful of fixed poses — a home, a pre-pick, a drop waypoint. Planning the trajectory between these poses on every iteration is pure overhead: the endpoints don't change.
Compute each pairwise trajectory once at Objective setup. Do not store the resulting MTCSolution directly — convert it to a JointTrajectory first with ConvertMtcSolutionToJointTrajectory and store that on the Blackboard. During the cycle, replay each stored trajectory via the ExecuteTrajectory Behavior.
SetupMTCFixedJointStateproduces an MTC state from explicit joint values — use it as the start of a plan that does not depend on the live robot state.SetupMTCPlanToJointStateplans to a target joint state from that fabricated start.SetupMTCFromSolutionchains a task off the end of a prior solution, so you can build a precomputed sequence of several hops.
ignore_collisions portOn SetupMTCFixedJointState, the ignore_collisions port (added in MoveIt Pro 9.1.0) defaults to true — collision checking is deferred to downstream stages that will see the runtime ACM. For precomputed trajectories, this is the correct default: the setup-time planning scene usually does not reflect the ACM you will have at execution time.
If any joint wraps past ±180°, raw inverse kinematics against a target pose can return a joint solution on the "wrong" side of a continuous joint. For robots with such joints, seed the plan with an MTCSolution from a live pose rather than constructing the start state from joint values.
Multiple stored joint trajectories can be blended into a single continuous motion via the BlendJointTrajectories Behavior. Useful when your cycle moves through several pre-known waypoints in sequence — blending eliminates the brief stop-start at each segment boundary you would otherwise see when replaying trajectories one at a time.
Hoist Setup Work Out of the Cycle Loop
Anything that does not change from iteration to iteration belongs outside the loop.
Two common offenders:
- Initial object registration — the first point-cloud registration positions the first part. On subsequent iterations, registration of the next part can happen in parallel with picking the current one (see below), so the only unavoidable registration is the first. Do it before the
Repeatdecorator, not inside it. - Move to pre-pick — if every iteration starts by moving from home to pre-pick, that motion is also iteration-invariant. The first one belongs in setup; later iterations can end at pre-pick instead of home.
Audit the body of your cycle loop and move anything that does not depend on per-iteration state up into the Objective's top-level sequence.
Plan or Perceive in Parallel With Motion
Planning the next motion and perceiving the next part do not need the arm to be idle. Run them concurrently with the current motion using the Parallel control node — see Parallel Motion Planning for the canonical pattern and the recommended failure_count / success_count settings.
Typical overlaps in a pick-and-place cycle:
- Plan (pre-pick → pick) in parallel with the previous motion.
SetupMTC*stages that use fabricated joint states for their start — not the live robot state — can run any time beforeExecuteMTCSolutionneeds the trajectory (ExecuteMTCTaskin older, now-deprecated Behavior Trees). - Register the next part in parallel with a long drop-off motion. The drop-jig-to-home or drop-bin-to-home motion is often several seconds; that is enough time to reacquire a point cloud and register the next part.
Point-cloud registration run concurrently with motion can be contaminated by the arm itself. If your camera's field of view includes the workspace, gate the perception Subtree on the arm being outside a known region before starting registration, rather than on a fixed delay.
Prefer SetupMTCPathIK for Straight-Line Segments
SetupMTCPlanToPose runs a full motion planner. For short straight-line segments — typical pre-pick → pick approaches, lift-offs, and Cartesian retreats — SetupMTCPathIK chains inverse kinematics along the path and is substantially faster for the same result.
Use SetupMTCPathIK when:
- The motion is a straight-line Cartesian translation, possibly with a fixed orientation.
- The path is short enough that IK continuity is reliable.
Use SetupMTCPlanToPose when:
- The motion must avoid obstacles and requires a planner's search.
- The start and goal are far apart or separated by collision geometry.
SetupMTCBatchPoseIK handles the case of IK across a list of target poses in one call.
Mutate the Allowed Collision Matrix Directly
Allowing a single pair of objects to collide — gripper fingers against the part during a grasp — does not require an MTC task. But the built-in Behaviors that update the Allowed Collision Matrix (ACM) are all MTC-based:
SetupMTCIgnoreCollisionsBetweenObjectsSetupMTCUpdateGroupCollisionRuleSetupMTCUpdateObjectCollisionRule
Each of these stands up a short MTC task, which adds setup overhead on top of the actual ACM update. A direct call to the /apply_planning_scene service takes a fraction of the time for the same result.
If this optimization matters on your hot path, implement the direct call as a custom Behavior that builds an ApplyPlanningScene request with is_diff = true and an allowed_collision_matrix field listing the pairs to allow or disallow.
Choose Waypoints Near the Next Required Pose
A home waypoint on the opposite side of the workspace from your drop zone forces every cycle to cross the workspace twice. Pick a clear intermediate pose near the drop zone as your between-iterations rest position — the arm no longer has to travel to and from a distant home on every cycle.
Similar audit: any waypoint inserted "to be safe" (to avoid a collision you are no longer concerned about, to reach a pose that is no longer in the cycle) is paying for motion that no longer earns its keep. Remove it and replan.
Diagnostic Checklist
When a customer reports that cycle time regressed or is "slower than it should be":
- Confirm the scope. Time one iteration of the inner cycle, not the whole Objective. Include the
Repeatbody, exclude setup and teardown. - Collect a steady-state sample. Run at least 10 iterations. Use the fastest one as your working measurement — it reflects the best case with warm caches.
- Compute the ceiling. Sum
EXECUTION:bars. If the total is within 10% of the ceiling, stop — you are at the motion floor. Further work belongs in path planning, not Behavior Tree structure. - Identify the biggest non-EXECUTION bar. That is your first target.
- Apply one pattern at a time. Remeasure between changes. Pattern interactions exist — stacking three changes before measuring makes it impossible to tell which helped.
- Commit the one that helps. Revert the ones that don't. Move on to the next-largest bar.
Related Guides
- Parallel Motion Planning — the canonical pattern for concurrent plan-and-execute and perceive-and-move.
- Debug Task Constructor Planning Pipelines — diagnose MTC planning failures before optimizing their timing.
- Interacting With the Planning Scene — what each planning-scene Behavior does and when to use it.
- Creating Custom Behaviors — reference for implementing a direct-call ACM-update Behavior.