Three.js: Faster Vector3.prototype.projectOnVector

Created on 5 Jun 2016  Â·  12Comments  Â·  Source: mrdoob/three.js

Description of the problem

I think this implementation of Vector3.prototype.projectOnVector:

projectOnVector = function ( vector ) {
    var length = vector.length();
    var scalar = this.dot(vector) / (length * length);
    return vector.multiplyScalar(scalar);
};

would be significantly faster than the current implementation:

projectOnVector: function () {
    var v1, dot;
    return function projectOnVector( vector ) {
        if ( v1 === undefined ) v1 = new THREE.Vector3();
        v1.copy( vector ).normalize();
        dot = this.dot( v1 );
        return this.copy( v1 ).multiplyScalar( dot );
    };
}()

The allocation of a temporary Vector3 is stripped away, and it uses two function calls less.

Three.js version
  • [ ] Dev
  • [ ] r77
  • [x] r73

    Browser
  • [x] All of them

  • [ ] Chrome
  • [ ] Firefox
  • [ ] Internet Explorer

    OS
  • [x] All of them

  • [ ] Windows
  • [ ] Linux
  • [ ] Android
  • [ ] IOS
    Hardware Requirements (graphics card, VR Device, ...)

All 12 comments

Your version does not modify this, it modifies vector. Hence it is not generating the same output.

Minor detail! :P

How about now?

THREE.Vector3.prototype.projectOnVector2 = function projectOnVector2 (
vector ) {
var length = this.length();
var scalar = vector.dot(this) / (length * length);
return this.multiplyScalar(scalar);
};
Den 05/06/2016 23.19 skrev "WestLangley" [email protected]:

Your version does not modify this, it modifies vector. Hence it is not
generating the same output.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/mrdoob/three.js/issues/9088#issuecomment-223838266,
or mute the thread
https://github.com/notifications/unsubscribe/AAP_NEUilbqdjlX8ew-RdOrWgYFfLXRpks5qIz1PgaJpZM4Iubdj
.

That does not look correct, either.

What happened when you tested your code?

After fixing the implementation once again, I have turned my suggestion in
as a pull request. I have tested with 6 test cases that all succeed (see
pull request). Also, the argument vector remains unchanged.

https://github.com/mrdoob/three.js/pull/9089

Mvh. Jon Loldrup

On 6 June 2016 at 00:08, Jon Loldrup [email protected] wrote:

Minor detail! :P

How about now?

THREE.Vector3.prototype.projectOnVector2 = function projectOnVector2 (
vector ) {
var length = this.length();
var scalar = vector.dot(this) / (length * length);
return this.multiplyScalar(scalar);
};
Den 05/06/2016 23.19 skrev "WestLangley" [email protected]:

Your version does not modify this, it modifies vector. Hence it is not
generating the same output.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/mrdoob/three.js/issues/9088#issuecomment-223838266,
or mute the thread
https://github.com/notifications/unsubscribe/AAP_NEUilbqdjlX8ew-RdOrWgYFfLXRpks5qIz1PgaJpZM4Iubdj
.

@loldrup There are 3 points i'd like to make:
1 - can we see some benchmarks?
2 - most of this kind of optimization will be done by crankshaft/Xmonkey/whatever-compiler-you-have
3 - there is a reason why "normalize" is used instead of doing normalization in-line - it makes code more readable and avoids some duplication.

Lastly, it would be interesting to know what is your use-case for this?

  1. Benchmarks here*:

My version: 486 ms
Current version: 705 ms
(these numbers seems stable, plus minus 20ms, when reloading my code)

  1. I (and many others?) don't use transpilation. I only use browserify to
    bundle things up.
  2. I don't have an inlined equivalent to the normalize function in my code.
    Normalize operates in three dimensions whereas length * length isn't a
    3D'ish operation. Relevance must triumph here.
  3. My use case isn't performance intensive, but other peoples use cases
    might be.

Code used for benchmark:
let i, vecThis = new THREE.Vector3( 0, 0, 0 ), vecOther = new
THREE.Vector3( 0, 0, 0 );

    console.time("projectOnVector2");
    for ( i = 0; i < 10000000; i++ ) {

        vecThis.x = Math.random() + 1;
        vecThis.y = Math.random() + 1;
        vecThis.z = Math.random() + 1;
        vecOther.x = Math.random() + 1;
        vecOther.y = Math.random() + 1;
        vecOther.z = Math.random() + 1;

        vecThis = vecThis.projectOnVector2( vecOther );
    }
    console.timeEnd("projectOnVector2");

    console.time("projectOnVector");
    for ( i = 0; i < 10000000; i++ ) {

        vecThis.x = Math.random() + 1;
        vecThis.y = Math.random() + 1;
        vecThis.z = Math.random() + 1;
        vecOther.x = Math.random() + 1;
        vecOther.y = Math.random() + 1;
        vecOther.z = Math.random() + 1;

        vecThis = vecThis.projectOnVector( vecOther );
    }
    console.timeEnd("projectOnVector");

@loldrup https://github.com/loldrup There are 3 points i'd like to make:
1 - can we see some benchmarks?
2 - most of this kind of optimization will be done by
crankshaft/Xmonkey/whatever-compiler-you-have
3 - there is a reason why "normalize" is used instead of doing
normalization in-line - it makes code more readable and avoids some
duplication.

Lastly, it would be interesting to know what is your use-case for this?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/mrdoob/three.js/issues/9088#issuecomment-223913549,
or mute
the thread
https://github.com/notifications/unsubscribe/AAP_NDVoLXerizMqs94ubATmdmZvrmX8ks5qI-xWgaJpZM4Iubdj
.

3 - there is a reason why "normalize" is used instead of doing normalization in-line - it makes code more readable and avoids some duplication.

More readable does not mean faster.
In 3D the execution speed is the most important goal.

@loldrup
Thanks for clarifications. A few pointers on benchmarks:

  • Runtime optimizations typically kick in only after a code path has been invoked sufficient number of times (e.g. say 10,000 calls). For this a warm-up phase is useful.
  • Having "nop" in benchmarks can be useful to derive impact of function-under-test. In this case having not 2 implementations of "projectOnVector", but 3, where 3rd one doesn't result in much computation at all (preferably none, or just enough that compiler doesn't optimize it out entirely). This eliminates overhead of benchmark code (such as Math.random()).

thanks for the numbers, it does seem like a very significant speed-up.
@mrdumb
I think I won't be the only one to disagree, but this is more about design goals of a specific library, in this case "three.js", which is intended for ease of use, clarity and small footprint, I would argue that latter two are being negatively impacted by such optimizations. Not to say the optimization is bad though.

Regarding clarity:
Who says my version is less clear than the existing one? Do we have any
arguments that points towards this conclusion?
I found the current version hard to comprehend, because I first had to
figure out the 'immediately executed function' trick, and then understand
why there was a vector in a closure (it's there in order to provide reuse
of the memory allocation that the current version depends on).
To me it seems much simpler to not depend on a vector in a closure.
Also, my version is derived from the Wikipedia article on vector
projection, so anyone coming from there will see a 1:1 correspondence with
what they just read.

Regarding small footprint:
As my version is a few lines shorter, it wins on small footprint. Thus it
wins both in LOCs, in memory usage and in CPU time.

PS. thanks for the tips on benchmarking.
Den 06/06/2016 15.33 skrev "Alex Goldring" [email protected]:

@loldrup https://github.com/loldrup
Thanks for clarifications. A few pointers on benchmarks:

  • Runtime optimizations typically kick in only after a code path has
    been invoked sufficient number of times (e.g. say 10,000 calls). For this a
    warm-up phase is useful.
  • Having "nop" in benchmarks can be useful to derive impact of
    function-under-test. In this case having not 2 implementations of
    "projectOnVector", but 3, where 3rd one doesn't result in much computation
    at all (preferably none, or just enough that compiler doesn't optimize it
    out entirely). This eliminates overhead of benchmark code (such as
    Math.random()).

thanks for the numbers, it does seem like a very significant speed-up.
@mrdumb https://github.com/mrdumb
I think I won't be the only one to disagree, but this is more about design
goals of a specific library, in this case "three.js", which is intended for
ease of use, clarity and small footprint, I would argue that latter two are
being negatively impacted by such optimizations. Not to say the
optimization is bad though.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/mrdoob/three.js/issues/9088#issuecomment-223959501,
or mute the thread
https://github.com/notifications/unsubscribe/AAP_NDiFwzv76DZZ7hNRwwDQswOPjkwMks5qJCGxgaJpZM4Iubdj
.

"three.js" is intended for ease of use, clarity and small footprint

Vector3.prototype.projectOnVector is not something related to "ease of use and clarity".
Also, "more readable" leads to bigger footprint and slower execution.

@mrdumb thanks for sharing your opinion

thanks for the tips on benchmarking

@loldrup glad it was useful

With #9101 merged, I guess this should be closed?

Nice work @loldrup!

Was this page helpful?
0 / 5 - 0 ratings