开发者

Performance implications of using Functional style Javascript vs "procedural"

开发者 https://www.devze.com 2023-01-10 16:22 出处:网络
Has anybody done benchmarking, or can link to an article on this subject? Particularly interested in IE results, since usually JS performance is not a problem in other browsers.

Has anybody done benchmarking, or can link to an article on this subject? Particularly interested in IE results, since usually JS performance is not a problem in other browsers.

I would like to know how much slower it is to do something like:

var numbers = [1, 2, 3, 4, 5, 6, 7];
var results = numbers.map(function() 开发者_如何学编程{ 
  // do some stuff
});

instead of the typical:

var numbers = [1, 2, 3, 4, 5, 6, 7];
var results = [];

for (var i = 0; i < numbers.length; i++) {
  var number = numbers[i];
  var result;
  // do some stuff
  results.push(result);
}

I obviously prefer the functional style, but I assume the extra overhead of calling an extra function for each item could slow things down with big collections.

Thanks!


TL;DR: The older your syntax, the faster. Avoid functional programming when you're optimizing for speed.

I decided to come up with a benchmark that would include the common things that you might do with functional programming, which just produced some aggregations. The resulting benchmark can be run here. The following results are from late 2020, but by the time you're reading this, maybe some optimizations or something have been done, you can re-test it with this link, but if that link is dead, the code is also pasted below. I've used the results from Chrome, but while some browsers run things faster than others, they all maintain the same performance ordering. Turns out Firefox is way slower as of this writing, like, Chrome is 5x faster than Firefox. Edge is just as fast as Chrome. Safari is also just as fast as Chrome. Node is also just as fast as Chrome. Since Chrome, Safari, and node all run on the v8 engine, I suspect they'll always be the same speed:

https://jsbench.me/dvkgymlimu/1

The first function 'aggregate_functional', uses all the newest syntax, spread operators, reduce, etc. It is by far the slowest, running at around 10M ops/sec. Given that this is the slowest, but still runs a crazy 10 million times every second, I think this proves that the argument is moot for all but the most extreme cases. If 10 million times per second isn't fast enough for you, then read on.

The next function uses the "of" loop, and is FOUR TIMES FASTER than the functional version. I wasn't really expecting this level of performance boost.

The last function 'aggregate_imperative_idx', uses "old" JavaScript things, no spread operator, no "of" loop, just a indexed loop. If not for the "let" and "const" keywords, it should run on Netscape Navigator. I was happy to find that using "var" did not improve performance. That would have made me sad. It ended up being 8.2x faster than the functional implementation, at 82M ops/sec.



function aggregate_functional (numbers) {
  if (numbers.length == 0) {
    throw new Error("aggregate() requires at least one number")
  }
  const sum = numbers.reduce((s, n) => s += n)
  let min = Math.min(...numbers)
  let max = Math.max(...numbers)
  const average = sum / numbers.length
  return {
    sum,
    average,
    min,
    max,
  }
}

function aggregate_imperative (numbers) {
  if (numbers.length == 0) {
    throw new Error("aggregate() requires at least one number")
  }
  let sum = 0
  let min = numbers[0]
  let max = numbers[0]
  for (const val of numbers) {
    if (val < min) min = val
    if (val > max) max = val
    sum += val
  }
  const average = sum / numbers.length
  return {
    sum,
    average,
    min,
    max,
  }
}

function aggregate_imperative_idx (numbers) {
  if (numbers.length == 0) {
    throw new Error("aggregate() requires at least one number")
  }
  let sum = 0
  let min = numbers[0]
  let max = numbers[0]
  for (let i = 0; i < numbers.length; i++) {
    const val = numbers[i]
    if (val < min) min = val
    if (val > max) max = val
    sum += val
  }
  const average = sum / numbers.length
  return {
    sum,
    average,
    min,
    max,
  }
}


Not content with the lack of proof on this subject, I wrote a short benchmark. It's far from perfect but I think it answers the question.

I ran it in IE 8/win, and while the functional method is slower, it's never going to be the bottleneck in real code. (Unless you're doing stuff that you shouldn't be doing in the client anyway)

So I will be using the cleaner approach whenever I have to pick (yay)

(Best of 5)
Functional method: 453ms
Old school approach: 156ms

Array.prototype.map = function(fun) {
  var len = this.length >>> 0;
  if (typeof fun != "function")
    throw new TypeError();

  var res = new Array(len);
  var thisp = arguments[1];
  for (var i = 0; i < len; i++) {
    if (i in this)
      res[i] = fun.call(thisp, this[i], i, this);
  }

  return res;
};

/**
 *
 *
 */

// Initialize test array
var numbers = [];
for (var i = 0; i < 100000; i++) numbers.push(i);

// Benchmark!
var start = +new Date();

// Test 1
var results1 = numbers.map(function(num) {
  return num + num;
});

alert('1. Functional map:' + (start - new Date()));
start = +new Date();

// Test 2
var results2 = [];
for (var j = 0, l = numbers.length; j < l; j++) {
  var num = numbers[j];
  results2.push(num + num)
}

alert('1. Old school approach' + (start - new Date()));
start = +new Date();


This one is really interesting:

http://www.slideshare.net/madrobby/extreme-javascript-performance

However, in ECMAScript5-ready JS engines with native Array.map(), things may change drastically.


This is very interesting too:

http://documentcloud.github.com/underscore/test/test.html

The results vary from browser to browser, because underscore tries to use the native alternative where available.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号