[Draft] Mitigate JavaScript Object Allocation for Performance Gain #337
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.
This is a draft PR!
Completion status:
✅ rigid_body
✅ toi
❌ narrow_phase
❌ character_controller
❌ ray_cast_vehicle_controller
✅ collider
❌ multibody_joint
❌ impulse_joint
❌ contact
❌ ray
❌ point
❌ pid_controller
❌ shape
This PR is a spiritual continuation of #25 , except we are also now refactoring the Rust side of things, so it is possible to allocate 0 JS objects per call as opposed to 1.
Currently, every time
rigidBody.translation()
is called, 2 objects are created in JavaScript that have to be garbage collected. This goes forrigidBody.rotation()
as well and all the other methods that return objects to JavaScript.Here is the
translation()
method:rapier.js/src.ts/dynamics/rigid_body.ts
Lines 297 to 300 in 4d3b651
this.rawSet.rbTranslation(this.handle)
creates a newRawVector
object.VectorOps.fromRaw(res)
creates a newRapier.Vector3
object by essentially cloning the firstRawVector
object.In any 3D world you use Rapier.js with, you will need to call
translation()
androtation()
for each rigid body on every frame in order to synchronize your rendered objects (e.g. THREE.js meshes) to Rapier.js.So, in a hypothetical world with 1,000 rigid bodies running at 60fps, this is equivalent to 60,000 calls to
translation()
and 60,000 calls torotation()
per second. Furthermore, since these methods internally create 2 objects each, this is equivalent to 60,000 * 2 * 2 = 240,000 objects/second that need to be garbage collected, and this is ONLY if you calltranslation()
androtation()
once for each rigid body per frame. If you call the methods multiple times for each rigid body per frame, the object creation can easily reach millions per second.In this pull request, I have changed the implementation for these methods to give the user the option of providing their own pre-allocated "target" object, into which x/y/z values will be copied. I have also changed the Rust counterparts to these methods to NOT return
RawVector
objects, but rather to take ascratchBuffer
object in the form of aFloat32Array
so that Rust values can be copied there for JavaScript to read from.RigidBody.translation()
now looks like this:RigidBody (rigid_body.ts)
VectorOps (math.ts)
RawRigidBodySet (rigid_body.rs)
The user can utilize this new API like this. In the following example, only one object is allocated, even though we called
translation()
five times:Performance Study
Here are the performance gains for 1,000 rigid bodies simulated at 60 FPS with the new API:
Anecdotally, I have also experienced a great reduction in GC jank in Firefox especially, aside from the FPS gain.
Run the benchmark yourself here: https://jsfiddle.net/qyg058wd