Skip to content

perf: an unsatisfied relational field costs ~400x a satisfied one, on every assignment #564

Description

Summary

A relational assignment that finds nothing costs 366-431 us. One that succeeds costs
~1.0 us + 0.037 us per sibling. That is roughly 400x, and it is paid on every assignment,
not once.

Measured on 6000.4.6f1 through the MCP bridge during session 223 (PR #563), while decomposing the
relational hot path for #529.

siblings subject control (Transform, no relational fields) delta
0 (nothing to bind) 366-431 us 0.306 us 366-431 us
1 1.319 us 0.305 us 1.015 us
4 1.401 us 0.306 us 1.095 us
16 1.864 us 0.306 us 1.558 us
64 3.651 us 0.305 us 3.346 us

The control read 0.305-0.309 us across all ten measurements, which is what makes the deltas
trustworthy.

Cause

LogMissingComponentError reaches component.LogError(...), and Unity captures a stack trace
for Debug.LogError. That is the whole 400x. The interpolated message string is correctly inside
the if (!metadata.attribute.Optional) guard, so it is not the allocation — it is the log call
itself.

Why this is worth a ticket rather than "fix your prefab"

Two things make it reachable in normal use rather than only in a misconfigured scene.

1. For a collection field, "found nothing" is a normal state. All six collection sites set:

foundSibling = 0 < filteredCount;

and the caller then does:

if (!foundSibling)
{
    LogMissingComponentError(component, metadata, "sibling");
    AssignNullToSingleField(component, metadata);
}

So a Collider2D[] field on an object that legitimately has no colliders right now is an error,
every time it is assigned. Optional = true is the escape hatch and it defaults to false.

2. The cost is per assignment, not per object. A scene that assigns relational components across
many objects at load pays 366-431 us for each unsatisfied field. A few hundred of those is a
visible load-time stall whose only symptom is "there are some errors in the console" -- which reads
as a content problem, not a performance one.

What a fix might look like

Not obvious, and deliberately not prescribed here. Options worth measuring against each other:

  1. Rate-limit or coalesce, e.g. report once per (type, field) rather than per assignment. Keeps
    the diagnostic, removes the repetition. Changes what a user sees in the console.
  2. Default Optional to true for collection fields specifically, since an empty collection is
    a valid answer in a way a null single reference is not. This is a behavior change and needs the
    owner's call.
  3. Leave the behavior and document the cost, so a consumer knows that a console full of these is
    also a load-time stall.

Option 1 is the only one that is purely additive. Whichever is chosen, the measurement above is the
before, and the after should be taken with the same control.

Note for whoever measures this

A benchmark whose fixture has no matching sibling is measuring the console, not the assignment
path -- the reading is three orders of magnitude out and looks like a catastrophic regression in the
code under test. That trap is recorded in .llm/skills/unity-api-costs.md.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions