Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Math.imul() faster than a regular multiplication (*) with few inputs, and slower with lots?

Tags:

javascript

I was playing around with the Math.imul() method and I found out it was faster with few inputs and slower with lots. Why is that?

(Maybe it has nothing to do with Math.imul() itself but that doesn't matter, I'm still interested in understanding the results I got anyway!)

The code:

const base_multiplier = 40;
const input_counts = [
    base_multiplier,
    base_multiplier * 10,
    base_multiplier * 100,
    base_multiplier * 1000
];
for (const input_count of input_counts) {

    const value_pairs = Array
        .from({ length: input_count })
        .map(() => [
            Math.round(Math.random() * 100),
            Math.round(Math.random() * 100)
        ])

    console.time(`${input_count} inputs | Standart multiplication`);
    eval('value_pairs.forEach(([x, y]) => x * y)')
    console.timeEnd(`${input_count} inputs | Standart multiplication`);

    console.time(`${input_count} inputs | Imul multiplication`);
    eval('value_pairs.forEach(([x, y]) => Math.imul(x, y))')
    console.timeEnd(`${input_count} inputs | Imul multiplication`);

}

The output with the Chrome's console:

40 inputs | Standart multiplication: 0.048ms
40 inputs | Imul multiplication: 0.043ms
400 inputs | Standart multiplication: 0.031ms
400 inputs | Imul multiplication: 0.063ms
4000 inputs | Standart multiplication: 0.826ms
4000 inputs | Imul multiplication: 3.604ms
40000 inputs | Standart multiplication: 0.834ms
40000 inputs | Imul multiplication: 0.898ms

The output with Node:

40 inputs | Standart multiplication: 0.510ms
40 inputs | Imul multiplication: 0.064ms
400 inputs | Standart multiplication: 0.569ms
400 inputs | Imul multiplication: 0.108ms
4000 inputs | Standart multiplication: 0.172ms
4000 inputs | Imul multiplication: 3.253ms
40000 inputs | Standart multiplication: 0.502ms
40000 inputs | Imul multiplication: 0.762ms
like image 223
Alexis_A Avatar asked Oct 18 '22 16:10

Alexis_A


1 Answers

The basic answer is that Math.imul and multiplication with * are not the same, and as such shouldn't really be compared like this. Math.imul performs 32 bit integer multiplication, but still must accept java script numbers as input. Thus it must perform additional operations to convert and or ensure that the input is integer. (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul )

This means that Math.imul(a,b) accepts Js floating point numbers and produces a result similar to Math.floor(a) * Math.floor(b)

To make the test a bit more correct, we could try to use Uint32 arrays, round the Math.random results, make sure that all allocations happen at the beginning of the loop and calculate a sum at the end to prevent the mul operations from being optimized away by the compiler.

The results are still not clear cut, and I suspect it comes down to the fact that imul speed gains from operating on integers are balanced against losses for conversion and function calls. And this balance is size dependent.

var base = 40;
var input = [base, base * 10, base * 100, base * 1000];
for (var j = 0; j < input.length; ++j) {
  var length = input[j];
  var arrayX = new Uint32Array(length);
  var arrayY = new Uint32Array(length);
  var im = new Uint32Array(length);
  var sm = new Uint32Array(length);

  for (var i = 0; i < input[j]; ++i) {
    arrayX[i] = Math.round(Math.random() * 100);
    arrayY[i] = Math.round(Math.random() * 100)
  }

  console.time(input[j] + ' inputs | Standard multiplication');
  for (var i = 0; i < length; ++i) {
    sm[i] = arrayX[i] * arrayY[i];
  }
  console.timeEnd(input[j] + ' inputs | Standard multiplication');

  console.time(input[j] + ' inputs | Imul multiplication');
  for (var i = 0; i < length; ++i) {
    im[i] = Math.imul(arrayX[i], arrayY[i]);
  }
  console.timeEnd(input[j] + ' inputs | Imul multiplication');

  // Prevent multiplication from being optimized away
  var sum = 0;
  for (var i = 0; i < length; ++i) {
    sum += sm[i];
    sum += im[i];
  }
  console.log(sum);
}

In Chrome on my machine, for input sizes of 40000 and above, imul is now marginally faster than standard * multiplication. While the 4000 size case is still slow for imul. However if the base is changed to 35, they are roughly equal. And if the base is changed to 55 * becomes much slower than imul.

40 inputs | Standard multiplication: 0.120ms
40 inputs | Imul multiplication: 0.035ms
203634
400 inputs | Standard multiplication: 0.045ms
400 inputs | Imul multiplication: 0.050ms
1967184
4000 inputs | Standard multiplication: 0.290ms
4000 inputs | Imul multiplication: 2.935ms
20062298
40000 inputs | Standard multiplication: 0.605ms
40000 inputs | Imul multiplication: 0.450ms
199130538


55 inputs | Standard multiplication: 0.130ms
55 inputs | Imul multiplication: 0.040ms
237716
550 inputs | Standard multiplication: 0.070ms
550 inputs | Imul multiplication: 0.060ms
2638770
5500 inputs | Standard multiplication: 3.175ms
5500 inputs | Imul multiplication: 0.095ms
27218460
55000 inputs | Standard multiplication: 0.850ms
55000 inputs | Imul multiplication: 0.575ms
277160962
like image 114
visibleman Avatar answered Oct 27 '22 00:10

visibleman