mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
103.02k stars 35.41k forks source link

Setting a vector value to a string causes huge raycaster slowdowns #12957

Closed elunty closed 6 years ago

elunty commented 6 years ago
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:

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
Browser
OS
moraxy commented 6 years ago

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

mrupley commented 6 years ago

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

elunty commented 6 years ago

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

mrupley commented 6 years ago

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.

elunty commented 6 years ago

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

razh commented 6 years ago

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
}
looeee commented 6 years ago

@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.

elunty commented 6 years ago

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

Mugen87 commented 6 years ago

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