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 :)

Populus
  • 7,470
  • 3
  • 38
  • 54
  • 1
    "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. – zerkms Mar 04 '14 at 20:37
  • @zerkms my tests cannot actually get any simpler <_< where would the noise come from? – Populus Mar 04 '14 at 20:39
  • 1
    http://jsperf.com/map-reduce-named-functions/2 - here is a better test. – zerkms Mar 04 '14 at 20:39
  • 1
    "where would the noise come from" --- "running map" vs "running map + creating anonymous function". The latter is expectedly slower. – zerkms Mar 04 '14 at 20:40
  • well that's what I expect, and it was mostly correct except with Firefox's `Array.map` implementation, which is slower with named functions... – Populus Mar 04 '14 at 20:41
  • "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. – zerkms Mar 04 '14 at 20:42
  • 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%. – Populus Mar 04 '14 at 20:48
  • 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. – zerkms Mar 04 '14 at 20:49
  • Also I want to ask why you think your modifications to the test is better with the named function being defined within the test. – Populus Mar 04 '14 at 20:49
  • 1
    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). – zerkms Mar 04 '14 at 20:50
  • 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. – Populus Mar 04 '14 at 20:53
  • 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. – zerkms Mar 04 '14 at 20:56
  • Yea you're right, my original testing intentions got skewed after I started adding tests to other things. – Populus Mar 04 '14 at 20:57
  • 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. – Sander Elias Dec 10 '14 at 05:23
  • 2
    @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 :P – Populus Dec 10 '14 at 15:36
  • I also created a benchmark some time ago and found that native map is the slowest. quit disappointing. http://jsben.ch/#/BQhED – EscapeNetscape Oct 20 '16 at 13:25
  • Sorry to be a party-pooper, but what's being asked here? – Tom Wright Jan 04 '18 at 14:07
  • @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. – Populus Feb 15 '18 at 22:07

2 Answers2

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.

or29544
  • 608
  • 5
  • 7
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])
Kevin Danikowski
  • 4,620
  • 6
  • 41
  • 75
  • 1
    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. – Populus Jul 19 '22 at 00:16
  • @Populus 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! – Kevin Danikowski Jul 19 '22 at 22:17