Why is precomputing sin (x) * slower * than using Math.sin () in Javascript?

I found what seems like an interesting anomaly in JavaScript. Which focuses on my attempts to speed up the calculation of trigonometric transformations by precomputing sin (x) and cos (x) and simply referring to precomputed values.

Intuitively, one would expect that the preliminary calculation would be faster than evaluating the functions Math.sin () and Math.cos () each time. Especially if your application design will use only a limited set of values ​​for the argument of trigger functions (in my case, integer degrees in the interval [0 °, 360 °), which is enough for my purposes here).

So, I did a little test. After preliminary calculation of the values ​​of sin (x) and cos (x), storing them in 360-element arrays, I wrote a short test function activated by a button on a simple test HTML page to compare the speed of the two approaches. One loop simply multiplies the value by the value of the precomputed element of the array, while the other loop multiplies the value by Math.sin ().

I expected that a precomputed loop would be noticeably faster than a loop involving a function call for a trigger function. To my surprise, the precomputed cycle was slower .

Here is the test function I wrote:

function MyTest()
{
var ITERATION_COUNT = 1000000;

var angle = Math.floor(Math.random() * 360);

var test1 = 200 * sinArray[angle];

var test2 = 200 * cosArray[angle];

var ref = document.getElementById("Output1");

var outData = "Test 1 : " + test1.toString().trim() + "<br><br>";
outData += "Test 2 : "+test2.toString().trim() + "<br><br>";

var time1 = new Date();     //Time at the start of the test

for (var i=0; i<ITERATION_COUNT; i++)
{
    var angle = Math.floor(Math.random() * 360);
    var test3 = (200 * sinArray[angle]);

//End i loop
}

var time2 = new Date();

//This somewhat unwieldy procedure is how we find out the elapsed time ...

var msec1 = (time1.getUTCSeconds() * 1000) + time1.getUTCMilliseconds();
var msec2 = (time2.getUTCSeconds() * 1000) + time2.getUTCMilliseconds();

var elapsed1 = msec2 - msec1;

outData += "Test 3 : Elapsed time is " + elapsed1.toString().trim() + " milliseconds<br><br>";

//Now comparison test with the same number of sin() calls ...

var time1 = new Date();

for (var i=0; i<ITERATION_COUNT; i++)
{
    var angle = Math.floor(Math.random() * 360);
    var test3 = (200 * Math.sin((Math.PI * angle) / 180));

//End i loop
}

var time2 = new Date();

var msec1 = (time1.getUTCSeconds() * 1000) + time1.getUTCMilliseconds();
var msec2 = (time2.getUTCSeconds() * 1000) + time2.getUTCMilliseconds();

var elapsed2 = msec2 - msec1;

outData += "Test 4 : Elapsed time is " + elapsed2.toString().trim() + " milliseconds<br><br>";

ref.innerHTML = outData;

//End function
}

, , , , , .

( 3 - , 4 - , Math.sin()):

1:

Test 3 : Elapsed time is 153 milliseconds

Test 4 : Elapsed time is 67 milliseconds

2:

Test 3 : Elapsed time is 167 milliseconds

Test 4 : Elapsed time is 69 milliseconds

3:

Test 3 : Elapsed time is 265 milliseconds

Test 4 : Elapsed time is 107 milliseconds

4:

Test 3 : Elapsed time is 162 milliseconds

Test 4 : Elapsed time is 69 milliseconds

, , , , , ? , , ?

- , , . , (, , , !), JavaScript, , . , , , JavaScript , , , , , .

, ?

Google Chrome:

60.0.3112.101 ( ) (64- )

64- Windows 7. Firefox, , , .

, JavaScript, , !

+6
3

.

, .
, .

{

    func : function (){
        var i,a,b;
        D2R = 180 / Math.PI
        b = 0;
        for (i = 0; i < count; i++ ) {
            // single test start
            a = (Math.random() * 360) | 0;
            b += Math.sin(a * D2R);
            // single test end
        }
    },
    name : "summed",
},{
    func : function (){
        var i,a,b;
        D2R = 180 / Math.PI;
        b = 0;
        for (i = 0; i < count; i++ ) {
            // single test start
            a = (Math.random() * 360) | 0;
            b = Math.sin(a * D2R);
            // single test end
        }
    },
    name : "unsummed",
},

=======================================
Performance test. : Optimiser check.
Use strict....... : false
Duplicates....... : 4
Samples per cycle : 100
Tests per Sample. : 10000
---------------------------------------------
Test : 'summed'
Calibrated Mean : 173µs ±1µs (*1) 11160 samples 57,803,468 TPS
---------------------------------------------
Test : 'unsummed'
Calibrated Mean : 0µs ±1µs (*1) 11063 samples Invalid TPS
----------------------------------------
Calibration zero : 140µs ±0µs (*)
(*) Error rate approximation does not represent the variance.
(*1) For calibrated results Error rate is Test Error + Calibration Error.
TPS is Tests per second as a calculated value not actual test per second.

( ).

, . , , .

javascript . , .

.

. , .

tests : [{
        func : function (){
            var i,a,b;
            b=0;
            for (i = 0; i < count; i++ ) {
                a = (Math.random() * 360) | 0;
                b += a;
            }
        },
        name : "Calibration",
    },{
        func : function (){
            var i,a,b;
            b = 0;
            for (i = 0; i < count; i++ ) {
                a = (Math.random() * 360) | 0;
                b += array[a];

            }
        },
        name : "lookup",
    },{
        func : function (){
            var i,a,b;
            b = 0;
            for (i = 0; i < count; i++ ) {
                a = (Math.random() * 360) | 0;
                b += Math.sin(a);
            }
        },
        name : "Sin",
    }
],

=======================================
Performance test. : Lookup compare to calculate sin.
Use strict....... : false
Data view........ : false
Duplicates....... : 4
Cycles........... : 1055
Samples per cycle : 100
Tests per Sample. : 10000
---------------------------------------------
Test : 'Calibration'
Calibrator Mean : 107µs ±1µs (*) 34921 samples
---------------------------------------------
Test : 'lookup'
Calibrated Mean : 6µs ±1µs (*1) 35342 samples 1,666,666,667TPS
---------------------------------------------
Test : 'Sin'
Calibrated Mean : 169µs ±1µs (*1) 35237 samples 59,171,598TPS
-All ----------------------------------------
Mean : 0.166ms Totals time : 17481.165ms 105500 samples
Calibration zero : 107µs ±1µs (*);
(*) Error rate approximation does not represent the variance.
(*1) For calibrated results Error rate is Test Error + Calibration Error.
TPS is Tests per second as a calculated value not actual test per second.

, . ??? . .

+2

, .

var countElement = document.getElementById('count');
var result1Element = document.getElementById('result1');
var result2Element = document.getElementById('result2');
var result3Element = document.getElementById('result3');

var floatArray = new Array(360);
var typedArray = new Float64Array(360);
var typedArray2 = new Float32Array(360);

function init() {
  for (var i = 0; i < 360; i++) {
    floatArray[i] = typedArray[i] = Math.sin(i * Math.PI / 180);
  }
  countElement.addEventListener('change', reset);
  document.querySelector('form').addEventListener('submit', run);
}

function test1(count) {
  var start = Date.now();
  var sum = 0;
  for (var i = 0; i < count; i++) {
    for (var j = 0; j < 360; j++) {
      sum += Math.sin(j * Math.PI / 180);
    }
  }
  var end = Date.now();
  var result1 = "sum=" + sum + "; time=" + (end - start);
  result1Element.textContent = result1;
}

function test2(count) {
  var start = Date.now();
  var sum = 0;
  for (var i = 0; i < count; i++) {
    for (var j = 0; j < 360; j++) {
      sum += floatArray[j];
    }
  }
  var end = Date.now();
  var result2 = "sum=" + sum + "; time=" + (end - start);
  result2Element.textContent = result2;
}

function test3(count) {
  var start = Date.now();
  var sum = 0;
  for (var i = 0; i < count; i++) {
    for (var j = 0; j < 360; j++) {
      sum += typedArray[j];
    }
  }
  var end = Date.now();
  var result3 = "sum=" + sum + "; time=" + (end - start);
  result3Element.textContent = result3;
}

function reset() {
  result1Element.textContent = '';
  result2Element.textContent = '';
  result3Element.textContent = '';
}

function run(ev) {
  ev.preventDefault();
  reset();
  var count = countElement.valueAsNumber;
  setTimeout(test1, 0, count);
  setTimeout(test2, 0, count);
  setTimeout(test3, 0, count);
}

init();
<form>
  <input id="count" type="number" min="1" value="100000">
  <input id="run" type="submit" value="Run">
</form>
<dl>
  <dt><tt>Math.sin()</tt></dt>
  <dd>Result: <span id="result1"></span></dd>
  <dt><tt>Array()</tt></dt>
  <dd>Result: <span id="result2"></span></dd>
  <dt><tt>Float64Array()</tt></dt>
  <dd>Result: <span id="result3"></span></dd>
</dl>
Hide result

, , , , , , . . , , :

Math.sin(): 652ms
(): 41ms
Float64Array(): 37ms

, , JIT . , Date.now() + .

+1

, ,

Jsbench shows that pre-computed array will be 13% faster than when usingMath.sin()

  • Computed Array: 86% (fastest 1480 ms)
  • Math.sin (): 100% (1718 ms)
  • Precomputed Typed Array: 87% (1493 ms)

Hope this helps!

0
source

All Articles