Repository navigation
impr: Access vector components through their backing fields - #3270
Draft
collectioneur wants to merge 1 commit into
Draft
collectioneur wants to merge 1 commit into
collectioneur wants to merge 1 commit into
Conversation
This branch has not been deployed
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.
Vectors keep their components in plain fields,
e0toe3. The named accessors (.x/.y/.z/.wand.r/.g/.b/.a) didn't read those fields directly, though. They went through the numeric index first:That extra hop is where the time went. Vectors extend
Arrayand callsuper(N), so each vector is an array of length N whose slots are all empty. To evaluatethis[0], the engine looks for element0on the vector itself, finds a hole, walks up the prototype chain and only then finds the0accessor on the class prototype. Engines don't optimize that path, so everyv.xpaid for a slow lookup just to reach a field that was right there.this.e0is an ordinary property read that the JIT caches and inlines.Setters took the same detour, and in Vec2 and Vec3 they also cast the value twice on the way (once in
set x, again inset 0).x/y/z/wandr/g/b/agetters and setters on Vec2, Vec3 and Vec4 now read and writee0–e3directly. Each setter casts the value once.v[0]), swizzles (v.xy) and the compiled buffer writer still go through the index accessors. I've left them for separate PRs.The CPU implementations of
stdfunctions read components through.x/.y/…, sostd.length,std.dotand similar functions get faster too.I built
typegpufrommainand from this branch and ran both withNODE_ENV=productionon Node 24 (V8) and Bun 1.3 (JavaScriptCore). Each number is the median of 7 fresh processes per build, alternating between the two builds:v.x + v.y + v.z, 100kvec3fv.x += 1(andy,z), 100kvec3fstd.length, 100kvec3fstd.dot, 100kvec3fThe last row is the CPU physics that the
jelly-sliderexample runs every frame (Slider.updatewithout the GPU writes). One frame does about 25k component reads and 6.6kd.vec2fconstructions, and the resulting positions are identical on both builds. Most of the remaining 2 ms is spent constructing those vectors, which this PR doesn't touch.Draft #3035 makes the same getter change as part of a much larger PR. This pulls it out on its own, together with the setters, so it can land independently. Whichever merges second will need a trivial rebase.
pnpm test:unitpasses locally, except for the Deno integration test inunplugin-typegpu, which needs Deno ≥ 2.8.