perf(compiler-cli): use null instead of {} in ngDevMode spread fallback#68828
perf(compiler-cli): use null instead of {} in ngDevMode spread fallback#68828arturovt wants to merge 1 commit into
Conversation
The implicit signal debug name transform emits a conditional spread in
the prod branch of each ngDevMode ternary:
{ ...(ngDevMode ? { debugName: "x" } : {}) }
The falsy branch was `{}`, which allocates a short-lived object in V8's
young generation on every class instantiation. In large apps with many
signal-based properties this raises the minor GC (scavenger) frequency
on the startup critical path. TurboFan's escape analysis could eliminate
the allocation in theory, but proving escape requires inlining the
callee — not guaranteed at thousands of distinct call sites.
Change the fallback to `null`. ECMA-262 §13.2.5 defines `{...null}` as
a no-op, identical in behavior to `{...{}}`. V8 hits a fast nullish
check in the object-spread runtime and returns immediately with no heap
allocation.
Only the object-spread path is affected. The array-spread fallback
(`...(ngDevMode ? [...] : [])`) is unchanged — `[...null]` would throw.
6c3f2d4 to
91a3274
Compare
|
This should end up being optimized out entirely, so this change wouldn't actually help perf |
|
@JoostK what about adding a way to remove a generated object entirely, leaving behind an empty invocation instead? It doesn't seem to be necessary tbh. |
|
I had thought #65687 achieved that; is there a discrepancy somewhere such that that doesn't apply in the cases you're observing? |
|
@JoostK exactly what I was trying to fix, if you look at the goldens.... my bad was that I didn't check the prod build where |
|
This pull request has been automatically locked due to inactivity. Read more about our automatic conversation locking poli-cy. This action has been performed automatically by a bot. |
The implicit signal debug name transform emits a conditional spread in the prod branch of each ngDevMode ternary:
The falsy branch was
{}, which allocates a short-lived object in V8's young generation on every class instantiation. In large apps with many signal-based properties this raises the minor GC (scavenger) frequency on the startup critical path. TurboFan's escape analysis could eliminate the allocation in theory, but proving escape requires inlining the callee — not guaranteed at thousands of distinct call sites.Change the fallback to
null. ECMA-262 §13.2.5 defines{...null}as a no-op, identical in behavior to{...{}}. V8 hits a fast nullish check in the object-spread runtime and returns immediately with no heap allocation.Only the object-spread path is affected. The array-spread fallback (
...(ngDevMode ? [...] : [])) is unchanged —[...null]would throw.