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:
- 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.
- 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.
- 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.
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.6f1through the MCP bridge during session 223 (PR #563), while decomposing therelational hot path for #529.
Transform, no relational fields)The control read 0.305-0.309 us across all ten measurements, which is what makes the deltas
trustworthy.
Cause
LogMissingComponentErrorreachescomponent.LogError(...), and Unity captures a stack tracefor
Debug.LogError. That is the whole 400x. The interpolated message string is correctly insidethe
if (!metadata.attribute.Optional)guard, so it is not the allocation — it is the log callitself.
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:
and the caller then does:
So a
Collider2D[]field on an object that legitimately has no colliders right now is an error,every time it is assigned.
Optional = trueis the escape hatch and it defaults tofalse.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:
the diagnostic, removes the repetition. Changes what a user sees in the console.
Optionaltotruefor collection fields specifically, since an empty collection isa valid answer in a way a null single reference is not. This is a behavior change and needs the
owner's call.
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.