map (v2)

Revision 2 of this benchmark created by Calvin Metcalf on


Description

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.

Setup

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;
    };

Test runner

Ready to run.

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

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by Nikolay Frantsev on
  • Revision 2: published by Calvin Metcalf on