Javascript performance of Array.map
Asked Answered
C

2

17

Just wrote up some test cases in jsperf to test the difference between named and anonymous functions while using Array.map and other alternatives.

http://jsperf.com/map-reduce-named-functions

(excuse the url name, there is no testing of Array.reduce in here, I named the test before fully deciding on what I wanted to test)

A simple for/while loop is obviously the fastest, I'm still surprised by the more than 10x slower Array.map though...

Then I tried the polyfill by mozilla https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Polyfill

Array.prototype.map = function(fun /*, thisArg */)
{
    "use strict";

    if (this === void 0 || this === null)
        throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function")
        throw new TypeError();

    var res = new Array(len);
    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++)
    {
        // NOTE: Absolute correctness would demand Object.defineProperty
        //       be used.  But this method is fairly new, and failure is
        //       possible only if Object.prototype or Array.prototype
        //       has a property |i| (very unlikely), so use a less-correct
        //       but more portable alternative.
        if (i in t)
            res[i] = fun.call(thisArg, t[i], i, t);
    }

    return res;
};

Then I tried a simple implementation that I wrote myself...

Array.prototype.map3 = function(callback /*, thisArg */) {
    'use strict';
    if (typeof callback !== 'function') {
        throw new TypeError();
    }

    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;

    for (var i = 0, len = this.length; i < len; i++) {
        this[i] = callback.call(thisArg, this[i], i, this);
    };
};

Summary of results:

From fastest to slowest:

  1. For simple/while (about the same)
  2. Map3 (my own implementation)
  3. Map2 (Mozilla polyfill)
  4. Array.map
  5. for in

Observations

An interesting note is that named functions are usually alittle faster than if you use anonymous functions (around 5%). But I noticed that the polyfill is slower with named functions in firefox, but faster in chrome, but chrome's own map implementation is slower with named functions... I tested this about 10x each, so even if it's not exactly intensive testing (which jsperf already does), unless my luck is that great it should be enough as a guideline.

Also, chrome's map function is up to 2x slower than firefox on my machine. Didn't expect that at all.

And... firefox's own Array.map implementation is slower than the Mozilla Polyfill... haha

I'm not sure why ECMA-262 specs state that map can be used for objects other than Arrays (http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.19). That makes the whole map function 3-4 times slower (as shown in my tests) as you need to check for property existence every loop...

Conclusion

There isn't really much difference between named and anonymous functions, if you consider that different browsers perform slightly differently.

At the end of the day, we shouldn't really micro-optimize too much, but I found this interesting :)

Cassy answered 4/3, 2014 at 20:35 Comment(18)
"An interesting note is that named functions are usually alittle faster than if you use anonymous functions " --- of course. The code creates anonymous function multiple times. So your performance tests are not correct since they have some additional noise.Docilu
@Docilu my tests cannot actually get any simpler <_< where would the noise come from?Cassy
jsperf.com/map-reduce-named-functions/2 - here is a better test.Docilu
"where would the noise come from" --- "running map" vs "running map + creating anonymous function". The latter is expectedly slower.Docilu
well that's what I expect, and it was mostly correct except with Firefox's Array.map implementation, which is slower with named functions...Cassy
"which is slower with named functions" --- it cannot be. You're passing a reference to a function. It doesn't matter where the reference comes from.Docilu
I guess my luck really was great, my 11th try still yielded FF being slower with named functions, but the 12th and 13th try said otherwise... But only very, very slightly faster, not even 1%.Cassy
so? < 1% difference can be caused by anything. Your CPU run some another process on the same core or browser was doing some internal routine.Docilu
Also I want to ask why you think your modifications to the test is better with the named function being defined within the test.Cassy
because the test case is run multiple times. If you take the function definition out of it - it's defined once. So you will measure not comparable cases. It's like you take 2 identical cars, remove all the wheels from one and then explain its slower speed by its wrong color (instead of explaining it by missing wheels).Docilu
That's what I wanted to test, to have a predefined callback function that you may use multiple times throughout your code, so redefining it every test would be against that. However I now get what you mean, as the test is unfair towards the anonymous function.Cassy
you're testing speed of creating function vs not creating function, not .map() speed. And obviously it's slower to create a function rather than not create it.Docilu
Yea you're right, my original testing intentions got skewed after I started adding tests to other things.Cassy
I just stumbled upon this test. Your native loops are doing something different then the Array.map(). I din't look into your own .map versions. However, array.map does NOT ALTER the original array, but instead delivers a modified copy of your array. While a for loop might still be faster, if you take that in to account the difference is not as large as your test suggests.Judijudicable
@SanderElias Thanks for the observation. But even with your revision to the tests, using Array.map is quite a bit slower than a simple for loop. Anyway as mentioned in previous comments my tests got off-topic from my original intentions, so take it with a grain of salt :PCassy
I also created a benchmark some time ago and found that native map is the slowest. quit disappointing. jsben.ch/#/BQhEDCognizant
Sorry to be a party-pooper, but what's being asked here?Progenitive
@TomWright haha sorry this was a while ago so I don't remember what I was thinking exactly, but I think I was going to ask about the differences in performance, but ended up writing the tests and posted my findings anyway. Should probably community wiki this if it's deemed worthy.Cassy
L
2

Well first of all, it's not a fair comparison. As you said, the proper javascript map is able to use objects, not just arrays. So you are basically comparing two completely different functions with different algorithms/results/inner workings.

Of course the proper javascript map is slower - it is designed to work over a larger domain than a simple for over an array.

L answered 2/3, 2015 at 13:26 Comment(0)
J
1

Just want to share some findings that I thought was relevant here. I had some issues with .map taking forever. Switching to hash map cut the speed tremendously.

We had a map function across 200k small objects, converting that map to a hashed object and recreating the array took it from 20min to 0.4s.

First approach (20min):

const newArr = arr1.map((obj) => {
  const context1 = arr2.find(o => o.id === obj.id)
  const context2 = arr3.find(o => o.id === obj.id)
  return { ...obj, context1, context2 }
})

New Approach

const newArrObj = {}
arr1.forEach(o => newArrObj[o.id] = o)
arr2.forEach(o => newArrObj[o.id].context1 = o)
arr3.forEach(o => newArrObj[o.id].context2 = o)

const users = []
arr1.forEach(o => users[users.length] = newArrObj[o.id])
Johannisberger answered 17/7, 2022 at 16:10 Comment(2)
Well you went from a O(N^2) solution to a O(N) one, and then add that you're redeclaring 2 anonymous functions per iteration makes the first approach pretty unoptimized to begin with. To make it more relevant to just the varying methods of using map, you could change the callbacks to your find to predeclared (named) functions and see if that makes much difference. What you showed here is an algorithmic change instead of different implementations/usages of map, which is still very important, and the correct solution for your case.Cassy
@Cassy great point, I did end up simplifying the find function to just finding a string in a string array, didn't think to actually make it a predeclared object, I'm sure it would have improved substantially. I did see that recreating the object did take some time, so that just addatively modifying the newArrObj in the second approach was still improved compared to reducing the O(N^2) complexity. but great point!Johannisberger

© 2022 - 2024 — McMap. All rights reserved.