perf: avoid iterator allocations in Frustum.setFromMat4#8775
Merged
Conversation
`const [m00, ..., m33] = matrix.data` destructures the Float32Array via
the iterator protocol, allocating a `{ value, done }` object per element
(16 per call) plus the iterator itself. `Frustum.setFromMat4` runs per
camera per layer per frame, so this accumulates into measurable GC
pressure under V8 sampling profiling of a typical scene.
Replace the destructuring with explicit indexed reads — same behaviour,
no iterator allocation, and the subtree that Frustum.setFromMat4 attributes
to (including the downstream Plane.normalize HeapNumber boxing) shrinks
in step.
Measured impact on a typical-scene allocation profile (1800 frames,
NullGraphicsDevice, V8 sampling heap profiler @ 128 B interval):
- Frustum.setFromMat4 subtree (selfSize incl. children): 98,160 B -> 36,216 B (-63%)
- Iterator `.next()` attribution under this site: ~22 KB -> 0 B
A targeted microbenchmark of `Frustum.setFromMat4` x 20,000 calls drops
from ~56 B/iter to ~45 B/iter; the residual is V8 HeapNumber boxing
inside `Plane.normalize` arithmetic and is unrelated to this change.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR optimizes a hot path in the math/culling layer by removing Float32Array destructuring from Frustum.setFromMat4, avoiding per-call iterator-related allocations and reducing GC pressure during per-frame frustum updates.
Changes:
- Replaced
const [m00, ..., m33] = matrix.datadestructuring with explicit indexed reads frommatrix.data. - Introduced a local
dalias formatrix.datato keep the indexed reads concise.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mvaligursky
approved these changes
May 26, 2026
Contributor
|
Down with syntactic sugar that harms performance!!!! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Frustum.setFromMat4was destructuring aFloat32Arraywithconst [m00, ..., m33] = matrix.data. JavaScript array destructuring uses the iterator protocol, so on every call V8 allocates one{ value, done }result object per element (16 per call) plus the iterator object itself.This function fires per-camera per-layer per-frame, so the iterator-result objects accumulated into measurable GC pressure on a representative scene workload — and even pulled additional V8 HeapNumber boxing into the Plane.normalize subtree downstream.
Replacing the destructuring with explicit indexed reads is behaviour-identical and eliminates the iterator allocations entirely.
Technical details
Measured impact
Captured via V8 sampling heap profiler (
node:inspectorHeapProfiler.startSampling) at 128 B interval, 1800 frames of a representative scene rendered againstNullGraphicsDevice:Frustum.setFromMat4subtree (selfSize incl. children).next()attribution under this siteA targeted microbenchmark of
Frustum.setFromMat4× 20,000 calls drops from ~56 B/iter to ~45 B/iter. The residual is V8 HeapNumber boxing inside the downstream Plane.normalize arithmetic — unrelated to this change.Public API changes
None.
Test plan
npm test)