Three.js: Setting a vector value to a string causes huge raycaster slowdowns

Created on 24 Dec 2017  路  9Comments  路  Source: mrdoob/three.js

Description of the problem

So I'm not sure what to even call this... I guess an oddity more than anything. I noticed this on a site i have when my raycasting was way slower than i had expected in certain scenarios. Here is the easiest way to reproduce:

  • Use this example: https://threejs.org/examples/webgl_geometry_terrain_raycast
  • Move your mouse around and notice the fps (whatever it may be)
  • Open up the console and enter: var testVector = new THREE.Vector3("1",2,3);
  • Move your mouse around again... there should be a huge drop in fps (45 --> 15 for me on my computer)
  • Set testVector.x back to some float/integer after making it a string.. it wont fix anything.

I just don't understand what the heck is going on. Was it my fault for creating a vector 3 with a string? Probably. But i just don't get how doing something like this grenades the performance of the entire scene. Maybe you could force a parseFloat on vector data?

Im not sure if this bug makes all threejs actions slower or just raycasting, but that is where i noticed it the most.

Extra notes: Doing the same thing with vector2/vector4 does nothing.

Three.js version
  • [ ] Dev
  • [x] r89 (example above)
  • [x] r79 (my app)
Browser
  • [ ] All of them
  • [x] Chrome
  • [ ] Firefox
  • [ ] Internet Explorer
OS
  • [ ] All of them
  • [x] Windows
  • [ ] macOS
  • [ ] Linux
  • [ ] Android
  • [ ] iOS

Most helpful comment

Passing in a string value converted Vector3-related code to be polymorphic rather than monomorphic:

https://github.com/davidmarkclements/v8-perf#polymorphic-vs-monomorphic-code

V8 and modern JS engines in general have something called hidden classes, which allow for optimizations as if JS were statically-typed.

For example, if you had this code (does nothing in particular):

const vector = new THREE.Vector3(1, 2, 3);
vector.x += 5;
vector.multiplyScalar(2);

const matrix = new THREE.Matrix4();
vector.applyMatrix4(matrix);

Then Chrome/V8 can reasonably assume that all of the Vector3 values are numeric and can execute the code as if this was the case:

class Vector3 {
  x: number;
  y: number;
  z: number;
}

When you set a property of a Vector3 instance to a string, V8 creates a new hidden class, and the existing optimizations are weakened:

class Vector3WithXAsAString {
  x: string;
  y: number;
  z: number;
}

The code-paths are updated to handle this class. Simplified, V8 ends up having to do something like this for method calls, property accesses, etc.:

if (vector is Vector3) {
  // use optimized version
} else if (vector3 is Vector3WithXAsAString) {
  // use slow version
}

All 9 comments

It certainly triggers _something_, judging by the performance graph. The same results with a current Firefox, btw.

Default:
normal

After switching to string (note all the orange boxes, it's always garbage collection ~1MB in size)
minor GC

https://github.com/mrdoob/three.js/pull/12960 I have a simple solution up to force parse into float.

But why would a vector3 that has nothing to do the actual logic of the example destroy all performance? Very strange.

It will need investigation, but it may have to do with the number of calculations performed by ray tracing on a vector and a potential casting of the string into a float many times.

But we don't pass that vector in the raycaster at all. It's on its own doing nothing

Passing in a string value converted Vector3-related code to be polymorphic rather than monomorphic:

https://github.com/davidmarkclements/v8-perf#polymorphic-vs-monomorphic-code

V8 and modern JS engines in general have something called hidden classes, which allow for optimizations as if JS were statically-typed.

For example, if you had this code (does nothing in particular):

const vector = new THREE.Vector3(1, 2, 3);
vector.x += 5;
vector.multiplyScalar(2);

const matrix = new THREE.Matrix4();
vector.applyMatrix4(matrix);

Then Chrome/V8 can reasonably assume that all of the Vector3 values are numeric and can execute the code as if this was the case:

class Vector3 {
  x: number;
  y: number;
  z: number;
}

When you set a property of a Vector3 instance to a string, V8 creates a new hidden class, and the existing optimizations are weakened:

class Vector3WithXAsAString {
  x: string;
  y: number;
  z: number;
}

The code-paths are updated to handle this class. Simplified, V8 ends up having to do something like this for method calls, property accesses, etc.:

if (vector is Vector3) {
  // use optimized version
} else if (vector3 is Vector3WithXAsAString) {
  // use slow version
}

@razh - your second link comes with the heading:

"Disclaimer: A lot of the information on this page is not accurate anymore."

Nonetheless, this is a very interesting point and certainly looks like a reasonable explanation.

Yeah very interesting.. wasn't aware optimization like that occurs behind the scenes.

Closing, since this is not an issue in the library. User have to ensure correct parameterization and type usage in their code.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yqrashawn picture yqrashawn  路  3Comments

akshaysrin picture akshaysrin  路  3Comments

zsitro picture zsitro  路  3Comments

konijn picture konijn  路  3Comments

donmccurdy picture donmccurdy  路  3Comments