map

JavaScript performance comparison

Revision 2 of this test case created by Calvin Metcalf

Info

Why is map so much slower then a while loop?

We can compare Array.prototype.map the specification as implemented here on mdn(judging by IE9 performance I don't think it's an accurate implementation for all browsers) and a quickMap function that just uses a basic while loop.

If we play around with specification map we can see that checking if the key is there (i.e. treating it like a sparse array) is the biggest slowdown in chrome, doubling the speed, the other big bottle neck is using function.call every time.

If we only use function.call when thisArg is defined, and don't bother to check if all keys are there then we can boost our speed back up to quickMap speeds.

Yay!, except the spec is pretty clear that this needs to be undefined if it isn't specified

Ok so we can create a version that binds it once right to avoid creating the new closure so meany times right ? WRONG! Chrome hates bind. Best I could come up with that didn't trash the speak was to create an empty object and call the function as a method of that, not spec but close.

Next up if it is called on a sparse array the function should only be called on defined keys, why don't we just use object keys to grab the keys that exist? WRONG! just checking once slows down Chrome somewhat and hoses IE9. I can't think of another way to check for array sparseness, and as we can also see checking if a key is defined doesn't make a difference.

So it seems to me if your not using this and not using a sparse array your better off rolling your own map.

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    var _len = 10000;
    var a = new Array(_len);
   
    for (var i = 0; i < _len; i++) {
      a[i] = i;
    }
   
    var f = function(a) {
        return a * a;
        }
       
       
       
    Array.prototype.qMap = function(fun) {
      var len = this.length;
      var out = new Array(len);
      var i = 0;
      while (i < len) {
        out[i] = fun(this[i], i, this);
        i++;
      }
      return out;
    }
   
    Array.prototype.sMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      if (thisArg) {
        _this = thisArg;
      }
      out = new Array(len);
      i = 0;
      while (i < len) {
        if (i in O) {
          out[i] = callback.call(_this, O[i], i, O);
        }
        i++;
      }
      return out;
    };
    Array.prototype.ncMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      if (thisArg) {
        _this = thisArg;
      }
      out = new Array(len);
      i = 0;
      while (i < len) {
        out[i] = callback.call(_this, O[i], i, O);
        i++;
      }
      return out;
    };
    Array.prototype.cinncMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      out = new Array(len);
      i = 0;
      if (thisArg) {
        _this = thisArg;
        while (i < len) {
          out[i] = callback.call(_this, O[i], i, O);
          i++;
        }
      } else {
        while (i < len) {
          out[i] = callback(O[i], i, O);
          i++;
        }
      }
      return out;
    };
    Array.prototype.cinMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      out = new Array(len);
      i = 0;
      var kValue, mappedValue;
      if (thisArg) {
        _this = thisArg;
   
        while (i < len) {
   
          if (i in O) {
            out[i] = callback.call(_this, O[i], i, O);
          }
          i++;
        }
   
      } else {
        while (i < len) {
          if (i in O) {
            out[i] = callback(O[i], i, O);
          }
          i++;
        }
      }
      return out;
    };
    Array.prototype.ncObjLitMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      if (thisArg) {
        _this = thisArg;
      } else {
        _this = {};
      };
      _this.callback = callback;
      out = new Array(len);
      i = 0;
      while (i < len) {
        out[i] = _this.callback(O[i], i, O);
        i++;
      }
      delete _this.callback;
      return out;
    };
    Array.prototype.bMap = function(cb, thisArg) {
      var _this, out, i, callback;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof cb !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      if (thisArg) {
        _this = thisArg;
      }
      callback = cb.bind(_this);
      out = new Array(len);
      i = 0;
      while (i < len) {
        out[i] = callback(O[i], i, O);
        i++;
      }
      return out;
    };
    Array.prototype.idMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      if (thisArg) {
        _this = thisArg;
      }
      out = new Array(len);
      i = 0;
      while (i < len) {
        if (typeof O[i] !== "undefined") {
          out[i] = callback.call(_this, O[i], i, O);
        }
        i++;
      }
      return out;
    };
    Array.prototype.okMap = function(callback, thisArg) {
      var _this, out, i, keys;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var keys = Object.keys(O);
      var len = O.keys >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      out = new Array(len);
      i = 0;
      if (thisArg) {
        _this = thisArg;
        while (i < len) {
          out[i] = callback.call(_this, O[keys[i]], keys[i], O);
          i++;
        }
      } else {
        while (i < len) {
          out[i] = callback(O[keys[i]], keys[i], O);
          i++;
        }
      }
      return out;
    };
    Array.prototype.ocisMap = function(callback, thisArg) {
      var _this, out, i;
      if (this == null) {
        throw new TypeError(" this is null or not defined");
      }
      var O = Object(this);
      var len = O.length >>> 0;
      if (typeof callback !== "function") {
        throw new TypeError(callback + " is not a function");
      }
      if (thisArg) {
        _this = thisArg;
      } else {
        _this = {};
      };
      _this.callback = callback;
      out = new Array(len);
      i = 0;
      if (len === Object.keys(O).length) {
        while (i < len) {
          out[i] = _this.callback(O[i], i, O);
          i++;
        }
      } else {
        while (i < len) {
          if (i in O) {
            out[i] = _this.callback(O[i], i, O);
          }
          i++;
        }
      }
      delete _this.callback;
      return out;
    };
};
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
map
var out = a.map(f);
pending…
Quick Map
var out = a.qMap(f);
pending…
Specification Map
var out = a.sMap(f);
pending…
call only if needed
var out = a.cinMap(f);
pending…
no check
var out = a.ncMap(f);
pending…
no check+call only if needed
var out = a.cinncMap(f);
pending…
No Check + Obj Literal This map
var out = a.ncObjLitMap(f);
pending…
Bind Map
var out = a.bMap(f);
pending…
If definded
var out = a.idMap(f);
pending…
use object keys to get subset
var out = a.okMap(f);
pending…
Only Check if Sparse
var out = a.ocisMap(f);
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

1 comment

Matt commented :

You're*

Add a comment