Sample Cache Test

JavaScript performance comparison

Test case created

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    function Sample(data, useCache) {
      var sample = util.parasiticExtend(
            //base object is an array
            [],
                 
            //the methods to expose
            [toPlainArray, mean, mean2],
           
            //no prop names
            [],
   
            //reset the constructor function
            Sample);
     
      //set up cache
      util.createCache(sample, useCache);
   
      //construct
      sample.push.apply(sample, data);
     
      return sample;
    }
   
    function toPlainArray() {
      util.removeCache(this);
      return util.parasiticRemove(this, Sample);
    }
   
    function mean() {
      if(this._useCache) {
        if(this._cacheHas('mean')) {
          return this._cache['mean'];
        }
        else {
          var sum  = this._cacheHas('sum')  ? this._cache['sum']  : this._cacheSet('sum', core.sum(this)),
              size = this._cacheHas('size') ? this._cache['size'] : this._cacheSet('size', this.length);
   
          return this._cacheSet('mean', sum/size);
        }
      }
      else {
        return core.sum(this)/this.length;
      }
    }
   
    function mean2() {
        return core.sum(this)/this.length;
    }
   
    var util = {};
   
    /********************************************** POLYFILLS *****************************************************/
   
    /**
     * Checks if an element is in an array.
     * Borrowed from jQuery. Uses the native indexOf if available.
     *
     * @param {Mixed} elem The element to search for
     * @param {Array} arr The array to search
     * @param {Integer} i (Optional) The index to start searching from
     * @return {Integer} The index at which the element was found; -1 if it wasn't.
     */

    util.inArray = function(elem, arr, i) {
      var len;
     
      if (arr) {
        if (Array.prototype.indexOf) {
          return Array.prototype.indexOf.call(arr, elem, i);
        }
     
        len = arr.length;
        i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
     
        for ( ; i < len; i++ ) {
           // Skip accessing in sparse arrays
          if (i in arr && arr[ i ] === elem) {
            return i;
          }
        }
      }
     
      return -1;
    };
   
    util.objectKeys = Object.keys ? Object.keys : (function () {
      var hasOwnProperty = Object.prototype.hasOwnProperty,
          hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
          dontEnums = [
            'toString',
            'toLocaleString',
            'valueOf',
            'hasOwnProperty',
            'isPrototypeOf',
            'propertyIsEnumerable',
            'constructor'
          ],
          dontEnumsLength = dontEnums.length;
   
      return function (obj) {
        if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
          throw new TypeError('Object.keys called on non-object');
        }
   
        var result = [];
   
        for (var prop in obj) {
          if (hasOwnProperty.call(obj, prop)) result.push(prop);
        }
   
        if (hasDontEnumBug) {
          for (var i=0; i < dontEnumsLength; i++) {
            if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
          }
        }
        return result;
      }
    })();
   
   
    /*************************************** TYPE-CHECKING & CONVERSION *******************************************/
   
    /**
     * Tests whether the provided argument is a native Array object
     *
     * @param {Mixed} arg The argument to test
     * @return {Boolean} Whether arg is an Array.
     */

    util.isArray = function(arg) {
      return Object.prototype.toString.call(arg) === "[object Array]";
    };
     
    /**
     * Tests whether the provided argument is a javascript Object.  
     * Uses Object.prototype.toString; does not count Arrays, Functions, etc.
     *
     * @param {Mixed} arg The argument to test
     * @return {Boolean} Whether arg is an Object.
     */

    util.isObject = function(arg) {
      return Object.prototype.toString.call(arg) === "[object Object]";
    };
     
    /**
     * Tests whether the arg is an object and (likely) not at DOM node.
     *
     * A highly stripped down verion of jQuery's is plain object.
     * Doesn't try very hard to filter out DOM nodes, host objects, etc.,
     * which we can reasonably assume won't be in numbers code.
     *
     * @param {Mixed} arg The arg to test
     * @return {Boolean} Whether the arg is an object and (likely) not a DOM node.
     */

    util.isPlainObject = function(arg) {
        return (util.isObject(arg) && !arg.nodeType);
    };
   
    /**
     * Tests whether the argument is an object or array with a given set of methods.
     *
     * @param {Mixed} arg The argument to test
     * @param {Array} methods An array of method names that the arg should have.
     * @return {Boolean} Whether arg is an object or array and has all the requested methods.
     */

    util.isObjWithMethods = function(arg, methods) {
        //allows objects or arrays
        if(typeof arg !== "object") {
          return false;
        }
   
        for (var i = 0, len = methods.length; i < len; i++) {
          if(typeof arg[methods[i]] !== "function") {
            return false;
          }
        }
     
        return true;
      }
   
    /**
     * Tests whether the argument is a number.
     * @param {Mixed} arg The argument to test
     * @param {Boolean} allowNaN Whether to count NaN as a number
     * @return {Boolean} Whether the arg is a number
     */

    util.isNumber = function(arg, allowNaN) {
      return (typeof arg === 'number') && (allowNaN || arg !== NaN);
    }
   
    /**
     * Tries to convert the provided arg to a primitive number.
     *
     * @param {Mixed} The arg to convert
     * @return {Number|False} The argument as a number, or false if conversion failed.
     */

    util.toNumber = function(arg) {
      var x = Number(arg);
   
      return x !== NaN ? x : false;
    }
   
   
    /*************************************** MISC. OBJECT MANIPULATION ********************************************/
   
    /**
     * Makes a deep clone of an object or array.
     * Adapted from jQuery; equivalent to jQuery.extend(true, []/{}, object);
     *
     * @param {Object|Array} The object or array to clone
     * @return {Object|Array} The clone
     */

    util.deepClone = function(object) {
   
      var name, src, copy, copyIsArray, clone,
          target = util.isArray(object) ? [] : {};
   
      //Extend the base object
      for (name in object) {
        src = target[ name ];
        copy = object[ name ];
   
        // Prevent never-ending loop
        if(target === copy) {
          continue;
        }
   
        //Clone and recurse if we're merging plain objects or arrays
        if( copy && (util.isPlainObject(copy) || (copyIsArray = util.isArray(copy))) ) {
          if ( copyIsArray ) {
            copyIsArray = false;
            clone = src && util.isArray(src) ? src : [];
          }
          else {
            clone = src && util.isPlainObject(src) ? src : {};
          }
   
          // Never move original objects, clone them
          target[ name ] = util.deepClone(copy, clone);
        }
         
        //Otherwise, just copy over the value unless it's undefined
        else if ( copy !== undefined ) {
          target[ name ] = copy;
        }
      }
   
      //Return the clone
      return target;
    }
   
    /**
     * Set up a sub class, with SubClassConstructor.__super__
     * pointing to SuperClassConstructor.prototype.
     *
     * @param {Function} The constructor function of the sub class
     * @param {Function} The constructor function of the super class.
     */

    util.extend = function(SubClassConstructor, SuperClassConstructor) {
   
      //create a new, dummy object that we can use to prototype chain in the SuperClass's
      //prototype while allowing us to bypass its constructor, which would potentially
      //throw errors (if it's missing args) and would drop instance vars onto our prototype.
      function Chainer() { this.constructor = SubClassConstructor; };
      Chainer.prototype = SuperClassConstructor.prototype;
      SubClassConstructor.prototype = new Chainer();
   
      //Set super as not an prototype property so that it's less susceptible to
      //reassignments of this. e.g. calling SubClass.x() might really produce a call of
      //subType.__proto__.__proto__.x() (i.e. an x on the prototype of the SuperClass),
      //and in that x `this` will point to the SubClass instance, even though the
      //SuperClass's method probably expects it to point to the SuperClass' instance.
      //We could make it an inherited property too (i.e. on SubClassConstructor.prototype),
      //but there's really no need. Any instance that wants to get access to it's parent's
      //prototype can do so with inst.constructor.__super__;
      SubClassConstructor.__super__  = SuperClassConstructor.prototype;
    };
   
   
    /****************************************** PARASITIC INHERITANCE *********************************************/
   
    /**
     * Extends the provided object parasitically.
     *
     * Takes the base object and attaches to it all the provided functions, using each's name as
     * the property it should go on in base. Also sets base's 'constructor' to the one provided,
     * and stores the names of all the methods added to base on constructor._typeMethods, such
     * that an object can later be duck-typed per the methods the constructor expects.
     *
     * @param {Object} base The base object to extend.
     * @param {Array} named_functions An array of named functions (i.e. defined
     * with an identifier, such that they have a .name property) to add to base.
     * @param {Array} prop_names An array of names of custom properties for the type.
     * @param {Function} constructor The function that's this "type"'s constructor.
     * @returns {Object} The modified base.
     */

    util.parasiticExtend = function(base, named_functions, prop_names, constructor) {
      var func;
     
      if(!util.isPlainObject(constructor._typeMethods)) {
        constructor._typeMethods = {};
      }
      if(!util.isPlainObject(constructor._typeProps)) {
        constructor._typeProps   = {};
      }
     
      for(var i = 0, len = named_functions.length; i < len; i++) {
        func = named_functions[i];
   
        if(typeof func !== 'function' || !func.name) {
          throw new Error("All provided elements in named_functions must be functions with a name");
        }
   
        else {
          base[func.name] = func;
          constructor._typeMethods[func.name] = true;
        }
      }
     
      for(i=0, len = prop_names.length; i < len; i++) {
        constructor._typeProps[prop_names[i]] = true;
      }
     
      base.constuctor = constructor;
      return base;
    }
   
    /**
     * Parasitically adds a function to an object under a different name it was defined with.
     *
     * @param {Object} base The base object to extend.
     * @param {Function} func The function to add
     * @param {String} name The property name under which this function should be added.
     * @param {Function} constructor The function that's this "type"'s constructor.
     */

    util.parasiticAddRenamedMethod = function(base, func, name, constructor) {
      base[name] = func;
   
      if(!util.isPlainObject(constructor._typeMethods)) { constructor._typeMethods = {}; }
      constructor._typeMethods[name] = true;
   
      return base;
    }
   
    /**
     * Checks whether an object is of a given type based on its constructor or
     * whether it has the methods from {@code parasiticExtend}.
     *
     * @param {Object} obj The object to test
     * @param {Function} constructor The constructor representing the type you're checking for
     * @returns {Boolean} Whether {@code obj} is of type {@code constructor}.
     */

    util.parasiticIs = function(obj, constructor) {
      return obj.constructor === constructor || util.isObjWithMethods(util.objectKeys(constructor._typeMethods));
    }
   
    /**
     * Removes any methods/properties added to the instance by {@code parasiticExtend}
     * and tries to reset the constructor property.
     * @param {Object} obj The object to "de-extend"
     * @param {Function} constructor The constructor function whose properties/methods to remove.
     * @param {Function} new_constructor (Optional) What to set the constructor property to; guessed if omitted.
     * @return {Object} The modified object.
     */

    util.parasiticRemove = function(obj, constructor, new_constructor) {
      for(var method in constructor._typeMethods) {
        delete obj[method];
      }
      for(var prop in constructor._typeProps) {
        delete obj[prop];
      }
   
      obj.constructor = new_constructor ? new_constructor : (util.isArray(obj) ? Array : Object);
     
      return obj;
    }
   
   
    /************************************************* CACHING ****************************************************/
   
    /**
     * Takes an instance and adds a cache object to it.
     *
     * Will add four methods to methodLocation: _cacheHas, _cacheSet, _cacheVoid, & setCaching.
     * Will add an _cache property to instance.
     *
     * @param {Object} instance The object to add the cache to.
     * @param {Boolean} useCache Whether the cache starts enabled or not.
     * @param {Object} methodLocation (Optional) Where to put the methods for checking,
     * setting, or voiding a member of the cache. If ommited, they'll be added to instance.
     */

    util.createCache = function(instance, useCache, methodLocation) {
      methodLocation = methodLocation ? methodLocation : instance;
     
      instance._cache = {};
      instance._useCache = useCache ? true : false;
      methodLocation._cacheHas = util.cacheHas;
      methodLocation._cacheSet = util.cacheSet;
      methodLocation._cacheVoid = util.cacheVoid;
      methodLocation.setCaching = util.setCaching;
    }
   
    /**
     * Removes from the given instance any properties added by {@createCache}.
     * @param {Object} instance The object whose cache related properties/methods to remove.
     */

    util.removeCache = function removeCache(instance) {
      delete instance["_cache"];
     
      //in case the methods were put on the instance
      delete instance["_cacheHas"];
      delete instance["_cacheSet"];
      delete instance["_cacheVoid"];
      delete instance["setCaching"];
    }
   
    /**
     * Turn on or off an instance cache creaed by {@code createCache}
     * @param {Boolean} useCaching Whether to turn caching on (true) or off (false).
     */

    util.setCaching = function setCaching(useCaching) {
      this._useCache = useCaching ? true : false;
   
      return this;
    }
   
    /**
     * Set a value in an instance cache created by {@code createCache}
     * @param {String} prop The property name
     * @param {Mixed} val The property's new value
     * @returns {Mixed} The property's new value.
     */

    util.cacheSet = function cacheSet(prop, val) {
      this._cache[prop] = val;
      return val;
    }
   
    /**
     * Whether a cache created by {@code createCache} has a cache of a given property.
     * @param {String} prop The property to check for.
     * @returns {Boolean} Whether this property has a value in the cache.
     */

    util.cacheHas = function cacheHas(prop) {
      return this._cache[prop] !== undefined;
    }
   
    /**
     * Invalidates a cached property in a cache created by {@code createCache}.
     * @param {String} prop Name of the property to invalidate.
     */

    util.cacheVoid = function cacheVoid(prop) {
      delete this._cache[prop];
    }
   
    var core = {};
   
    /**
     * Determine the summation of numbers in a given array
     *
     * @param {Array} collection of numbers
     * @return {Number} sum of numbers in array
     */

    core.sum = function (arr) {
      if (Object.prototype.toString.call(arr) === '[object Array]') {
        var total = 0;
        for (var i = 0 ; i < arr.length ; i++) {
          if (typeof(arr[i]) === 'number')
            total = total + arr[i];
          else
            throw new Error('All elements in array must be numbers');
        }
        return total;
      } else {
        throw new Error('Input must be of type Array');
      }
    };
   
    var sn = new Sample([1,3,41,3,5,22,4,3422,134,23,22,134,23,2], false);
    var sy = new Sample([1,3,41,3,5,22,4,3422,134,23,22,134,23,2], true);
};
</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
No cache
sn.mean();
sn.mean();
sn.mean();
pending…
Cache
sy.mean();
sy.mean();
sy.mean();
pending…
No cache but also no check if cache is on
sn.mean2();
sn.mean2();
sn.mean2();
pending…

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

Compare results of other browsers

0 comments

Add a comment