Repository navigation
Reuse the corner vectors in ExtrusionGeometry - #351
Conversation
|
Visit the preview URL for this PR (updated for commit 2d3bd84): https://gcode-preview--pr351-perf-reuse-corner-ve-zjb14i1a.web.app (expires Mon, 21 Sep 2026 03:04:25 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 59bd114ae4847b32c2bba0b68620b9069a3e3531 |
remcoder
left a comment
There was a problem hiding this comment.
Correct and clean. Verified locally (157/157 pass) and checked the aliasing carefully:
- The shared scratch vectors (tangent, cornerNormal, cornerBinormal, cross, nextDirection) are all distinct objects, and three.js crossVectors/copy read their arguments before writing
this, so no in-place aliasing hazard. generateSegmentconsumes the returned N/B ring fully inside its loop before the nextcomputeCornerAngles(i+1)call reuses the scratches — safe.- Behavior-identical: the
.clone().sub(P).normalize()chain is replaced by an equivalent.copy(...).sub(P).normalize(), same op order, no numeric drift.
Tests are well chosen — the 90-degree-turn test is exactly the right probe for stale shared state leaking between corners, and the straight-run test covers the mirror case. Both assert on rings, which is the unit the sharing affects.
Non-blocking: computeCornerAngles still returns [P, N, B] referencing shared scratch, so a future caller that stores N/B past the next call would silently break. A one-line JSDoc note (e.g. "returned vectors are scratch and must be consumed before the next call") would prevent that footgun, but it's not required for this change.
computeCornerAngles runs once per point and allocated five Vector3 every call: tangent, N, B, vec, and a clone of the next point. On the 3.5 MB 3DBenchy that is 520,185 short lived objects per tube render. They are all fully overwritten before use and generateSegment consumes the result before the next call, so a single shared set does the job. Tube geometry generation drops from 103ms to 94ms, and the garbage goes away entirely. Positions and normals compared element by element against the previous implementation across 500 geometries, all identical.
6079a5b to
2d3bd84
Compare
|
I applied your suggestion |
computeCornerAnglesruns once per point on every path, and allocates fiveVector3on each call:None of them outlive the call. Every one is fully overwritten before it is read, and
generateSegmentconsumes the returned[P, N, B]before the next call happens — so one shared set at constructor scope does the same work.Benchmark
3.5 MB 3DBenchy: 163,474 lines → 7023 paths → 104,037 points.
Vector3allocated per tube renderChrome, median of 7 runs after warm-up, building every extrusion geometry in the model.
The wall-clock saving is the smallest of the three
ExtrusionGeometrywins, but it removes half a million short-lived objects from every render, which is the kind of garbage that shows up as jank rather than as throughput.Correctness
Positions and normals compared element-by-element against a reimplementation of the previous allocating version across 500 geometries — all identical, no tolerance. Model renders unchanged.
Sharing scratch state is exactly the kind of change that breaks quietly, so the two tests added target that failure mode directly rather than the timing:
npm run checkpasses: 12 test files, typecheck, lint.Related
Two other independent optimizations in the same file: #349 pre-sizes the geometry buffers, #350 caches the ring of sines and cosines. All three touch
ExtrusionGeometry, so whichever merge later will need a small rebase. Together they take tube geometry generation from 103 ms to 39 ms.Assisted by Claude Code - Opus 5