Skip to content

Remove per-step heap allocations and shared statics from the RT path#15

Closed
joecga4 wants to merge 1 commit into
aschellenberg74:masterfrom
joecga4:perf/per-step-allocations
Closed

Remove per-step heap allocations and shared statics from the RT path#15
joecga4 wants to merge 1 commit into
aschellenberg74:masterfrom
joecga4:perf/per-step-allocations

Conversation

@joecga4

@joecga4 joecga4 commented Jul 6, 2026

Copy link
Copy Markdown

For RTHS the fixed cost per analyze(1, dt) step matters more than asymptotic solver speed. This PR removes the heap allocations, redundant copies and non-reentrant statics found while profiling the per-step path:

  • EEGeneric/EETruss: responses assembled component-wise into preallocated members (no operator+/- temporaries); trial-change check compares against DBL_EPSILON component-wise instead of building a difference vector; inertia paths use preallocated scratch.
  • ShadowExpSite: communication buffers preallocated in setSize() (removes the first-step latency spike).
  • ExperimentalSetup: caches whether all trial/out/ctrl/daq factors are 1.0 and skips the scaling passes in that common case.
  • ExperimentalSite::setTrialResponse/setDaqResponse: pointer-equality guards — LocalExpSite passed the site's own vectors, copying them onto themselves every step.
  • ExperimentalElement: the default getDisp/getVel/getAccel/getTime returned function-local statics shared between all elements and sized by whichever called first (latent bug with elements of different numDOF); per-instance members now.
  • ESTwoActuators2d: the static Vectors of the 4 transformations become preallocated members; rotations use addMatrixVector() instead of the heap-allocating operator*.

Validated: OneBayFrame local and distributed (3 processes) outputs bit-identical to the pre-change baseline.

🤖 Generated with Claude Code

For real-time hybrid simulation the fixed cost per analyze(1, dt) step
matters more than asymptotic solver speed; these are the allocation and
copy removals validated in the RTHS profiling work:

- EEGeneric/EETruss: no heap allocations per step. Responses are
  assembled component-wise into preallocated members (const Vector&
  passthroughs, no operator+/- temporaries), the trial-change check
  compares component-wise against DBL_EPSILON instead of building a
  difference vector, and the inertia paths use preallocated scratch.
- ShadowExpSite: communication buffers are preallocated in setSize(),
  removing the first-step latency spike when the buffers used to be
  grown inside the first send/recv.
- ExperimentalSetup: caches whether all trial/out/ctrl/daq factors are
  1.0 and skips the scaling passes entirely in that (common) case.
- ExperimentalSite::setTrialResponse/setDaqResponse: pointer-equality
  guards; LocalExpSite passed the site's own vectors, copying them
  onto themselves every step.
- ExperimentalElement: the default getDisp/getVel/getAccel/getTime
  returned function-local statics shared between ALL elements and
  sized by whichever called first (latent bug with elements of
  different numDOF); per-instance members now.
- ESTwoActuators2d: the static Vectors of the 4 transformations become
  preallocated members and the rotation uses addMatrixVector() instead
  of the heap-allocating operator*.

Validated: OneBayFrame local and distributed (3 processes) outputs
bit-identical to the pre-change baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joecga4 joecga4 closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant