/*! * Benchmark.js * Copyright 2010-2011 Mathias Bynens * Based on JSLitmus.js, copyright Robert Kieffer * Modified by John-David Dalton * Available under MIT license */ (function(window) { /** Detect DOM0 timeout API (performed at the bottom) */ var HAS_TIMEOUT_API, /** Detect Adobe AIR environment */ IN_AIR = isClassOf(window.runtime, 'ScriptBridgingProxyObject'), /** Detect Java environment */ IN_JAVA = isClassOf(window.java, 'JavaPackage'), /** Used to integrity check compiled tests */ EMBEDDED_UID = +new Date, /** Used to skip initialization of the Benchmark constructor */ HEADLESS = function() { }, /** Used to avoid hz of Infinity */ CYCLE_DIVISORS = { '1': 4096, '2': 512, '3': 64, '4': 8, '5': 0 }, /** * T-Distribution two-tailed critical values for 95% confidence * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm */ T_DISTRIBUTION = { '1': 12.706,'2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447, '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179, '13': 2.160, '14': 2.145, '15': 2.131, '16': 2.120, '17': 2.110, '18': 2.101, '19': 2.093, '20': 2.086, '21': 2.080, '22': 2.074, '23': 2.069, '24': 2.064, '25': 2.060, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042, 'Infinity': 1.960 }, /** Internal cached used by various methods */ cache = { 'counter': 0 }, /** Used in Benchmark.hasKey() */ hasOwnProperty = cache.hasOwnProperty, /** Used to convert array-like objects to arrays */ slice = [].slice, /** Used generically when invoking over queued arrays */ shift = aloClean([].shift), /** Math shortcuts */ abs = Math.abs, max = Math.max, min = Math.min, pow = Math.pow, sqrt = Math.sqrt; /*--------------------------------------------------------------------------*/ /** * Benchmark constructor. * @constructor * @param {String} name A name to identify the benchmark. * @param {Function} fn The test to benchmark. * @param {Object} [options={}] Options object. * @example * * // basic usage * var bench = new Benchmark(fn); * * // or using a name first * var bench = new Benchmark('foo', fn); * * // or with options * var bench = new Benchmark('foo', fn, { * * // displayed by Benchmark#toString if `name` is not available * 'id': 'xyz', * * // called when the benchmark starts running * 'onStart': onStart, * * // called after each run cycle * 'onCycle': onCycle, * * // called when aborted * 'onAbort': onAbort, * * // called when a test errors * 'onError': onError, * * // called when reset * 'onReset': onReset, * * // called when the benchmark completes running * 'onComplete': onComplete, * * // compiled/called before the test loop * 'setup': setup, * * // compiled/called after the test loop * 'teardown': teardown * }); * * // or options only * var bench = new Benchmark({ * * // benchmark name * 'name': 'foo', * * // benchmark test function * 'fn': fn * }); * * // a test's `this` binding is set to the benchmark instance * var bench = new Benchmark('foo', function() { * 'My name is '.concat(this.name); // My name is foo * }); */ function Benchmark(name, fn, options) { // juggle arguments var me = this; if (isClassOf(name, 'Object')) { // 1 argument options = name; } else if (isClassOf(name, 'Function')) { // 2 arguments options = fn; fn = name; } else { // 3 arguments me.name = name; } // initialize if not headless if (name != HEADLESS) { setOptions(me, options); fn = me.fn || (me.fn = fn); fn.uid || (fn.uid = ++cache.counter); me.created = +new Date; me.stats = extend({ }, me.stats); me.times = extend({ }, me.times); } } /** * Suite constructor. * @constructor * @member Benchmark * @param {String} name A name to identify the suite. * @param {Object} [options={}] Options object. * @example * * // basic usage * var suite = new Benchmark.Suite; * * // or using a name first * var suite = new Benchmark.Suite('foo'); * * // or with options * var suite = new Benchmark.Suite('foo', { * * // called when the suite starts running * 'onStart': onStart, * * // called between running benchmarks * 'onCycle': onCycle, * * // called when aborted * 'onAbort': onAbort, * * // called when a test errors * 'onError': onError, * * // called when reset * 'onReset': onReset, * * // called when the suite completes running * 'onComplete': onComplete * }); */ function Suite(name, options) { // juggle arguments var me = this; if (isClassOf(name, 'Object')) { options = name; } else { me.name = name; } setOptions(me, options); } /*--------------------------------------------------------------------------*/ /** * Wraps an array function to ensure array-like-objects (ALO) are empty when their length is `0`. * @private * @param {Function} fn The array function to be wrapped. * @returns {Function} The new function. */ function aloClean(fn) { return function() { var me = this, result = fn.apply(me, arguments); // fixes IE<9 and IE8 compatibility mode bugs if (!me.length) { delete me[0]; } return result; }; } /** * Executes a function asynchronously or synchronously. * @private * @param {Object} me The benchmark instance passed to `fn`. * @param {Function} fn Function to be executed. * @param {Boolean} [async=false] Flag to run asynchronously. */ function call(me, fn, async) { // only attempt asynchronous calls if supported if (async && HAS_TIMEOUT_API) { me.timerId = setTimeout(function() { delete me.timerId; fn(); }, me.CYCLE_DELAY * 1e3); } else { fn(); } } /** * Clocks the time taken to execute a test per cycle (secs). * @private * @param {Object} me The benchmark instance. * @returns {Number} The time taken. */ function clock() { var applet, args, fallback, proto = Benchmark.prototype, timers = [], timerNS = Date, msRes = getRes('ms'), timer = { 'ns': timerNS, 'res': max(0.0015, msRes), 'unit': 'ms' }, code = 'var r$,s$,m$=this,i$=m$.count,f$=m$.fn;#{setup}#{start};while(i$--){|}#{end};#{teardown}return{time:r$,uid:"$"}|m$.teardown&&m$.teardown();|f$()|m$.setup&&m$.setup();|n$'; clock = function(me) { var result, fn = me.fn, compiled = fn.compiled, count = me.count; if (applet) { // repair nanosecond timer try { timerNS.nanoTime(); } catch(e) { // use non-element to avoid issues with libs that augment them timerNS = new applet.Packages.nano; } } if (compiled == null || compiled) { try { if (!compiled) { // insert test body into the while-loop compiled = createFunction(args, interpolate(code[0], { 'setup': getSource(me.setup) }) + getSource(fn) + interpolate(code[1], { 'teardown': getSource(me.teardown) }) ); // determine if compiled code is exited early, usually by a rogue // return statement, by checking for a return object with the uid me.count = 1; compiled = fn.compiled = compiled.call(me, timerNS).uid == EMBEDDED_UID && compiled; me.count = count; } if (compiled) { result = compiled.call(me, timerNS).time; } } catch(e) { me.count = count; compiled = fn.compiled = false; } } // fallback to simple while-loop when compiled is false if (!compiled) { result = fallback.call(me, timerNS).time; } return result; }; function getRes(unit) { var measured, start, count = 30, divisor = 1e3, sample = []; // get average smallest measurable time while (count--) { if (unit == 'us') { divisor = 1e6; if (timerNS.stop) { timerNS.start(); while(!(measured = timerNS.microseconds())); } else { start = timerNS(); while(!(measured = timerNS() - start)); } } else if (unit == 'ns') { divisor = 1e9; start = timerNS.nanoTime(); while(!(measured = timerNS.nanoTime() - start)); } else { start = new timerNS; while(!(measured = new timerNS - start)); } // check for broken timers (nanoTime may have issues) // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/ if (measured > 0) { sample.push(measured); } else { sample.push(Infinity); break; } } // convert to seconds return getMean(sample) / divisor; } // detect nanosecond support from a Java applet each(window.document && document.applets || [], function(element) { if (timerNS = applet = 'nanoTime' in element && element) { return false; } }); // or the exposed Java API // http://download.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime() try { timerNS = java.lang.System; } catch(e) { } // check type in case Safari returns an object instead of a number try { if (typeof timerNS.nanoTime() == 'number') { timers.push({ 'ns': timerNS, 'res': getRes('ns'), 'unit': 'ns' }); } } catch(e) { } // detect Chrome's microsecond timer: // enable benchmarking via the --enable-benchmarking command // line switch in at least Chrome 7 to use chrome.Interval try { if (timerNS = new (window.chrome || window.chromium).Interval) { timers.push({ 'ns': timerNS, 'res': getRes('us'), 'unit': 'us' }); } } catch(e) { } // detect Node's microtime module: // npm install microtime try { if (timerNS = typeof require == 'function' && !window.require && require('microtime').now) { timers.push({ 'ns': timerNS, 'res': getRes('us'), 'unit': 'us' }); } } catch(e) { } // pick timer with highest resolution timerNS = (timer = reduce(timers, function(timer, other) { return other.res < timer.res ? other : timer; }, timer)).ns; // restore ms res if (timer.unit == 'ms') { timer.res = msRes; } // remove unused applet if (timer.unit != 'ns' && applet) { applet.parentNode.removeChild(applet); applet = null; } // error if there are no working timers if (timer.res == Infinity) { throw new Error('Benchmark.js was unable to find a working timer.'); } // use API of chosen timer if (timer.unit == 'ns') { code = interpolate(code, { 'start': 's$=n$.nanoTime()', 'end': 'r$=(n$.nanoTime()-s$)/1e9' }); } else if (timer.unit == 'us') { code = interpolate(code, timerNS.stop ? { 'start': 's$=n$.start()', 'end': 'r$=n$.microseconds()/1e6' } : { 'start': 's$=n$()', 'end': 'r$=(n$()-s$)/1e6' }); } else { code = interpolate(code, { 'start': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3' }); } // inject uid into variable names to avoid collisions with // embedded tests and create non-embedding fallback code = code.replace(/\$/g, EMBEDDED_UID).split('|'); args = code.pop(); fallback = createFunction(args, interpolate(code[0], { 'setup': code.pop() }) + code.pop() + interpolate(code[1], { 'teardown': code.pop() }) ); // resolve time to achieve a percent uncertainty of 1% proto.MIN_TIME || (proto.MIN_TIME = max(timer.res / 2 / 0.01, 0.05)); return clock.apply(null, arguments); } /** * Creates a function from the given arguments string and body. * @private * @param {String} args The comma separated function arguments. * @param {String} body The function body. * @returns {Function} The new function. */ function createFunction() { var scripts, prop = 'c' + EMBEDDED_UID; createFunction = function(args, body) { var parent = scripts[0].parentNode, script = document.createElement('script'); script.appendChild(document.createTextNode('Benchmark.' + prop + '=function(' + args + '){' + body + '}')); parent.removeChild(parent.insertBefore(script, scripts[0])); return [Benchmark[prop], delete Benchmark[prop]][0]; }; // fix JaegerMonkey bug // http://bugzil.la/639720 try { scripts = document.getElementsByTagName('script'); createFunction = createFunction('', 'return ' + EMBEDDED_UID)() == EMBEDDED_UID && createFunction; } catch (e) { createFunction = false; } createFunction || (createFunction = Function); return createFunction.apply(null, arguments); } /** * Wraps a function and passes `this` to the original function as the first argument. * @private * @param {Function} fn The function to be wrapped. * @returns {Function} The new function. */ function methodize(fn) { return function() { return fn.apply(this, [this].concat(slice.call(arguments))); }; } /** * Gets the critical value for the specified degrees of freedom. * @private * @param {Number} df The degrees of freedom. * @returns {Number} The critical value. */ function getCriticalValue(df) { return T_DISTRIBUTION[Math.round(df) || 1] || T_DISTRIBUTION.Infinity; } /** * Computes the arithmetic mean of a sample. * @private * @param {Array} sample The sample. * @returns {Number} The mean. */ function getMean(sample) { return reduce(sample, function(sum, x) { return sum + x; }, 0) / sample.length || 0; } /** * Gets the source code of a function. * @private * @param {Function} fn The function. * @returns {String} The function's source code. */ function getSource(fn) { return trim((/^[^{]+{([\s\S]*)}\s*$/.exec(fn) || 0)[1] || '') .replace(/([^\n])$/, '$1\n'); } /** * Sets the options of a benchmark. * @private * @param {Object} me The benchmark instance. * @param {Object} [options={}] Options object. */ function setOptions(me, options) { options = extend(extend({ }, me.constructor.options), options); me.options = each(options, function(value, key) { // add event listeners if (/^on[A-Z]/.test(key)) { each(key.split(' '), function(key) { me.on(key.slice(2).toLowerCase(), value); }); } else { me[key] = value; } }); } /*--------------------------------------------------------------------------*/ /** * A bare-bones `Array#forEach`/`for-in` own property solution. * Callbacks may terminate the loop by explicitly returning `false`. * @static * @member Benchmark * @param {Array|Object} object The object to iterate over. * @param {Function} callback The function called per iteration. * @returns {Array|Object} Returns the object iterated over. */ function each(object, callback) { var i = -1, length = object.length; if (hasKey(object, 'length') && length > -1 && length < 4294967296) { while (++i < length) { if (i in object && callback(object[i], i, object) === false) { break; } } } else { for (i in object) { if (hasKey(object, i) && callback(object[i], i, object) === false) { break; } } } return object; } /** * Copies own/inherited properties of a source object to the destination object. * @static * @member Benchmark * @param {Object} destination The destination object. * @param {Object} [source={}] The source object. * @returns {Object} The destination object. */ function extend(destination, source) { source || (source = { }); for (var key in source) { destination[key] = source[key]; } return destination; } /** * A generic bare-bones `Array#filter` solution. * @static * @member Benchmark * @param {Array} array The array to iterate over. * @param {Function|String} callback The function/alias called per iteration. * @returns {Array} A new array of values that passed callback filter. * @example * * // get odd numbers * Benchmark.filter([1, 2, 3, 4, 5], function(n) { * return n % 2; * }); // -> [1, 3, 5]; * * // get fastest benchmarks * Benchmark.filter(benches, 'fastest'); * * // get slowest benchmarks * Benchmark.filter(benches, 'slowest'); * * // get benchmarks that completed without erroring * Benchmark.filter(benches, 'successful'); */ function filter(array, callback) { var result; if (callback == 'successful') { // callback to exclude errored or unrun benchmarks callback = function(bench) { return bench.cycles; }; } else if (/^(?:fast|slow)est$/.test(callback)) { // get successful, sort by period + margin of error, and filter fastest/slowest result = filter(array, 'successful').sort(function(a, b) { a = a.stats; b = b.stats; return (a.mean + a.ME > b.mean + b.ME ? 1 : -1) * (callback == 'fastest' ? 1 : -1); }); result = filter(result, function(bench) { return !result[0].compare(bench); }); } return result || reduce(array, function(result, value, index) { return callback(value, index, array) ? result.push(value) && result : result; }, []); } /** * Converts a number to a more readable comma-separated string representation. * @static * @member Benchmark * @param {Number} number The number to convert. * @returns {String} The more readable string representation. */ function formatNumber(number) { number = String(number).split('.'); return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') + (number[1] ? '.' + number[1] : ''); } /** * Checks if an object has the specified key as a direct property. * @static * @member Benchmark * @param {Object} object The object to check. * @param {String} key The key to check for. * @returns {Boolean} Returns `true` if key is a direct property, else `false`. */ function hasKey(object, key) { var result, parent = (object.constructor || Object).prototype; // for modern browsers object = Object(object); if (isClassOf(hasOwnProperty, 'Function')) { result = hasOwnProperty.call(object, key); } // for Safari 2 else if (cache.__proto__ == Object.prototype) { object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0]; } // for others (not as accurate) else { result = key in object && !(key in parent && object[key] === parent[key]); } return result; } /** * A generic bare-bones `Array#indexOf` solution. * @static * @member Benchmark * @param {Array} array The array to iterate over. * @param {Mixed} value The value to search for. * @returns {Number} The index of the matched value or `-1`. */ function indexOf(array, value) { var result = -1; each(array, function(v, i) { if (v === value) { result = i; return false; } }); return result; } /** * Invokes a method on all items in an array. * @static * @member Benchmark * @param {Array} benches Array of benchmarks to iterate over. * @param {String|Object} name The name of the method to invoke OR options object. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. * @returns {Array} A new array of values returned from each method invoked. * @example * * // invoke `reset` on all benchmarks * Benchmark.invoke(benches, 'reset'); * * // invoke `emit` with arguments * Benchmark.invoke(benches, 'emit', 'complete', listener); * * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks * Benchmark.invoke(benches, { * * // invoke the `run` method * 'name': 'run', * * // pass a single argument * 'args': true, * * // treat as queue, removing benchmarks from front of `benches` until empty * 'queued': true, * * // called before any benchmarks have been invoked. * 'onStart': onStart, * * // called between invoking benchmarks * 'onCycle': onCycle, * * // called after all benchmarks have been invoked. * 'onComplete': onComplete * }); */ function invoke(benches, name) { var args, async, bench, queued, i = 0, options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop }, result = slice.call(benches, 0); function execute() { var listeners; if (async) { // use `next` as a listener bench.on('complete', next); listeners = bench.events['complete']; listeners.splice(0, 0, listeners.pop()); } // execute method result[i] = bench[name].apply(bench, args); // if synchronous return true until finished return async || next(); } function next() { var last = bench; bench = false; if (async) { last.removeListener('complete', next); last.emit('complete'); } // choose next benchmark if not exiting early if (options.onCycle.call(benches, last) !== false) { if (queued) { // use generic shift shift.call(benches); bench = benches[0]; } else { bench = benches[++i]; } } if (bench) { if (async) { call(bench, execute, async); } else { return true; } } else { options.onComplete.call(benches, last); } // when async the `return false` will cancel the rest of the "complete" // listeners because they were called above and when synchronous it will // end the while-loop return false; } // juggle arguments if (isClassOf(name, 'String')) { args = slice.call(arguments, 2); } else { options = extend(options, name); name = options.name; args = isArray(args = 'args' in options ? options.args : []) ? args : [args]; queued = options.queued; } // async for use with Benchmark#run only if (name == 'run') { async = (args[0] == null ? Benchmark.prototype.DEFAULT_ASYNC : args[0]) && HAS_TIMEOUT_API; } // start iterating over the array if (bench = benches[0]) { options.onStart.call(benches, bench); if (async) { call(bench, execute, async); } else { result.length = 0; while (execute()); } } return result; } /** * Modify a string by replacing named tokens with matching object property values. * @static * @member Benchmark * @param {String} string The string to modify. * @param {Object} object The template object. * @returns {String} The modified string. * @example * * Benchmark.interpolate('#{greet} #{who}!', { * 'greet': 'Hello', * 'who': 'world' * }); // -> 'Hello world!' */ function interpolate(string, object) { string = string == null ? '' : string; each(object || { }, function(value, key) { string = string.replace(RegExp('#\\{' + key + '\\}', 'g'), String(value)); }); return string; } /** * Determines if the given value is an array. * @static * @member Benchmark * @param {Mixed} value The value to check. * @returns {Boolean} Returns `true` if value is an array, else `false`. */ function isArray(value) { return isClassOf(value, 'Array'); } /** * Checks if an object is of the specified class. * @static * @member Benchmark * @param {Object} object The object. * @param {String} name The name of the class. * @returns {Boolean} Returns `true` if of the class, else `false`. */ function isClassOf(object, name) { return object != null && {}.toString.call(object).slice(8, -1) == name; } /** * Host objects can return type values that are different from their actual * data type. The objects we are concerned with usually return non-primitive * types of object, function, or unknown. * @static * @member Benchmark * @param {Mixed} object The owner of the property. * @param {String} property The property to check. * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`. */ function isHostType(object, property) { return !/^(?:boolean|number|string|undefined)$/ .test(typeof object[property]) && !!object[property]; } /** * Creates a string of joined array values or object key-value pairs. * @static * @member Benchmark * @param {Array|Object} object The object to operate on. * @param {String} [separator1=','] The separator used between key-value pairs. * @param {String} [separator2=': '] The separator used between keys and values. * @returns {String} The joined result. */ function join(object, separator1, separator2) { var pairs = []; if (isArray(object)) { pairs = object; } else { separator2 || (separator2 = ': '); each(object, function(value, key) { pairs.push(key + separator2 + value); }); } return pairs.join(separator1 || ','); } /** * A generic bare-bones `Array#map` solution. * @static * @member Benchmark * @param {Array} array The array to iterate over. * @param {Function} callback The function called per iteration. * @returns {Array} A new array of values returned by the callback. */ function map(array, callback) { return reduce(array, function(result, value, index) { result.push(callback(value, index, array)); return result; }, []); } /** * A no-operation function. * @static * @member Benchmark */ function noop() { // no operation performed } /** * Retrieves the value of a specified property from all items in an array. * @static * @member Benchmark * @param {Array} array The array to iterate over. * @param {String} property The property to pluck. * @returns {Array} A new array of property values. */ function pluck(array, property) { return map(array, function(object) { return object[property]; }); } /** * A generic bare-bones `Array#reduce` solution. * @static * @member Benchmark * @param {Array} array The array to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} accumulator Initial value of the accumulator. * @returns {Mixed} The accumulator. */ function reduce(array, callback, accumulator) { each(array, function(value, index) { accumulator = callback(accumulator, value, index, array); }); return accumulator; } /** * A generic bare-bones `String#trim` solution. * @static * @member Benchmark * @param {String} string The string to trim. * @returns {String} The trimmed string. */ function trim(string) { return String(string).replace(/^\s+/, '').replace(/\s+$/, ''); } /*--------------------------------------------------------------------------*/ /** * Aborts all benchmarks in the suite. * @name abort * @member Benchmark.Suite * @returns {Object} The suite instance. */ function abortSuite() { var me = this; me.aborted = true; me.running = false; invoke(me, 'abort'); me.emit('abort'); return me; } /** * Adds a test to the benchmark suite. * @member Benchmark.Suite * @param {String} name A name to identify the benchmark. * @param {Function} fn The test to benchmark. * @param {Object} [options={}] Options object. * @returns {Object} The benchmark instance. * @example * * // basic usage * suite.add(fn); * * // or using a name first * suite.add('foo', fn); * * // or with options * suite.add('foo', fn, { * 'onCycle': onCycle, * 'onComplete': onComplete * }); */ function add(name, fn, options) { var me = this, bench = new Benchmark(HEADLESS); Benchmark.call(bench, name, fn, options); me.push(bench); me.emit('add', bench); return me; } /** * Creates a new suite with cloned benchmarks. * @name clone * @member Benchmark.Suite * @param {Object} options Options object to overwrite cloned options. * @returns {Object} The new suite instance. */ function cloneSuite(options) { var me = this, result = new me.constructor(extend(extend({ }, me.options), options)); // copy own properties each(me, function(value, key) { if (!hasKey(result, key)) { result[key] = /^\d+$/.test(key) ? value.clone() : value; } }); return result.reset(); } /** * A bare-bones `Array#filter` solution. * @name filter * @member Benchmark.Suite * @param {Function|String} callback The function/alias called per iteration. * @returns {Object} A new suite of benchmarks that passed callback filter. */ function filterSuite(callback) { var me = this, result = new me.constructor; result.push.apply(result, filter(me, callback)); return result; } /** * Resets all benchmarks in the suite. * @name reset * @member Benchmark.Suite * @returns {Object} The suite instance. */ function resetSuite() { var me = this; me.aborted = me.running = false; invoke(me, 'reset'); me.emit('reset'); return me; } /** * Runs the suite. * @name run * @member Benchmark.Suite * @param {Boolean} [async=false] Flag to run asynchronously. * @param {Boolean} [queued=false] Flag to treat benchmarks as a queue. * @returns {Object} The suite instance. */ function runSuite(async, queued) { var me = this; me.reset(); me.running = true; invoke(me, { 'name': 'run', 'args': async, 'queued': queued, 'onStart': function(bench) { me.emit('start', bench); }, 'onCycle': function(bench) { if (bench.error) { me.emit('error', bench); } return !me.aborted && me.emit('cycle', bench); }, 'onComplete': function(bench) { me.running = false; me.emit('complete', bench); } }); return me; } /*--------------------------------------------------------------------------*/ /** * Registers a single listener of a specified event type. * @member Benchmark, Benchmark.Suite * @param {String} type The event type. * @param {Function} listener The function called when the event occurs. * @returns {Object} The benchmark instance. * @example * * // basic usage * bench.addListener('cycle', listener); * * // register a listener for multiple event types * bench.addListener('start cycle', listener); */ function addListener(type, listener) { var me = this, events = me.events || (me.events = { }); each(type.split(' '), function(type) { (events[type] || (events[type] = [])).push(listener); }); return me; } /** * Executes all registered listeners of a specified event type. * @member Benchmark, Benchmark.Suite * @param {String} type The event type. */ function emit(type) { var me = this, args = slice.call(arguments, 1), events = me.events, listeners = events && events[type] || [], successful = true; each(listeners, function(listener) { if (listener.apply(me, args) === false) { successful = false; return successful; } }); return successful; } /** * Unregisters a single listener of a specified event type. * @member Benchmark, Benchmark.Suite * @param {String} type The event type. * @param {Function} listener The function to unregister. * @returns {Object} The benchmark instance. * @example * * // basic usage * bench.removeListener('cycle', listener); * * // unregister a listener for multiple event types * bench.removeListener('start cycle', listener); */ function removeListener(type, listener) { var me = this, events = me.events; each(type.split(' '), function(type) { var listeners = events && events[type] || [], index = indexOf(listeners, listener); if (index > -1) { listeners.splice(index, 1); } }); return me; } /** * Unregisters all listeners of a specified event type. * @member Benchmark, Benchmark.Suite * @param {String} type The event type. * @returns {Object} The benchmark instance. * @example * * // basic usage * bench.removeAllListeners('cycle'); * * // unregister all listeners for multiple event types * bench.removeListener('start cycle'); */ function removeAllListeners(type) { var me = this, events = me.events; each(type.split(' '), function(type) { (events && events[type] || []).length = 0; }); return me; } /*--------------------------------------------------------------------------*/ /** * Aborts the benchmark without recording times. * @member Benchmark * @returns {Object} The benchmark instance. */ function abort() { var me = this; if (me.running) { if (me.timerId && HAS_TIMEOUT_API) { clearTimeout(me.timerId); delete me.timerId; } // set running as NaN so reset() will detect it as falsey and *not* call abort(), // but *will* detect it as a change and fire the onReset() callback me.running = NaN; me.reset(); me.aborted = true; me.emit('abort'); } return me; } /** * Creates a new benchmark using the same test and options. * @member Benchmark * @param {Object} options Options object to overwrite cloned options. * @returns {Object} The new benchmark instance. * @example * * var bizarro = bench.clone({ * 'name': 'doppelganger' * }); */ function clone(options) { var me = this, result = new me.constructor(me.fn, extend(extend({ }, me.options), options)); // copy own properties each(me, function(value, key) { if (!hasKey(result, key)) { result[key] = value; } }); return result.reset(); } /** * Determines if the benchmark's period is smaller than another. * @member Benchmark * @param {Object} other The benchmark to compare. * @returns {Number} Returns `1` if smaller, `-1` if larger, and `0` if indeterminate. */ function compare(other) { // unpaired two-sample t-test assuming equal variance // http://en.wikipedia.org/wiki/Student's_t-test // http://www.chem.utoronto.ca/coursenotes/analsci/StatsTutorial/12tailed.html var a = this.stats, b = other.stats, df = a.size + b.size - 2, pooled = (((a.size - 1) * a.variance) + ((b.size - 1) * b.variance)) / df, tstat = (a.mean - b.mean) / sqrt(pooled * (1 / a.size + 1 / b.size)), near = abs(1 - a.mean / b.mean) < 0.055 && a.RME < 3 && b.RME < 3; // check if the means aren't close and the t-statistic is significant return !near && abs(tstat) > getCriticalValue(df) ? (tstat > 0 ? -1 : 1) : 0; } /** * Reset properties and abort if running. * @member Benchmark * @returns {Object} The benchmark instance. */ function reset() { var changed, pair, me = this, source = extend(extend({ }, me.constructor.prototype), me.options), pairs = [[source, me]]; function check(value, key) { var other = pair[1][key]; if (value && isClassOf(value, 'Object')) { pairs.push([value, other]); } else if (!isClassOf(value, 'Function') && key != 'created' && value != other) { pair[1][key] = value; changed = true; } } if (me.running) { // no worries, reset() is called within abort() me.abort(); me.aborted = source.aborted; } else { // check if properties have changed and reset them while (pairs.length) { each((pair = pairs.pop(), pair[0]), check); } if (changed) { me.emit('reset'); } } return me; } /** * Displays relevant benchmark information when coerced to a string. * @member Benchmark * @returns {String} A string representation of the benchmark instance. */ function toString() { var me = this, error = me.error, hz = me.hz, stats = me.stats, size = stats.size, pm = IN_JAVA ? '+/-' : '\xb1', result = me.name || me.id || (''); if (error) { result += ': ' + join(error); } else { result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm + stats.RME.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)'; } return result; } /*--------------------------------------------------------------------------*/ /** * Computes stats on benchmark results. * @private * @param {Object} me The benchmark instance. * @param {Boolean} [async=false] Flag to run asynchronously. */ function compute(me, async) { var queue = [], sample = [], runCount = me.INIT_RUN_COUNT; function enqueue(count) { while (count--) { queue.push(me.clone({ 'computing': me, 'events': { 'start': [update], 'cycle': [update] } })); } } function update() { // port changes from clone to host var clone = this; if (me.running) { if (clone.cycles) { // the me.count and me.cycles props of the host are updated in cycle() below me.hz = clone.hz; me.times.period = clone.times.period; me.INIT_RUN_COUNT = clone.INIT_RUN_COUNT; me.emit('cycle'); } else if (clone.error) { me.abort(); me.error = clone.error; me.emit('error'); } else { // on start clone.count = me.INIT_RUN_COUNT; } } else if (me.aborted) { clone.abort(); } } function evaluate(clone) { var mean, moe, rme, sd, sem, variance, now = +new Date, times = me.times, aborted = me.aborted, elapsed = (now - times.start) / 1e3, maxedOut = elapsed > me.MAX_TIME_ELAPSED, size = sample.push(clone.times.period), varOf = function(sum, x) { return sum + pow(x - mean, 2); }; // exit early for aborted or unclockable tests if (aborted || clone.hz == Infinity) { maxedOut = !(size = sample.length = queue.length = 0); } // simulate onComplete and enqueue additional runs if needed if (queue.length < 2) { // sample mean (estimate of the population mean) mean = getMean(sample); // sample variance (estimate of the population variance) variance = reduce(sample, varOf, 0) / (size - 1); // sample standard deviation (estimate of the population standard deviation) sd = sqrt(variance); // standard error of the mean (aka the standard deviation of the sampling distribution of the sample mean) sem = sd / sqrt(size); // margin of error moe = sem * getCriticalValue(size - 1); // relative margin of error rme = (moe / mean) * 100 || 0; // if time permits, increase sample size to reduce the margin of error if (!maxedOut) { enqueue(1); } else { // set host values if (!aborted) { me.running = false; times.stop = now; times.elapsed = elapsed; extend(me.stats, { 'ME': moe, 'RME': rme, 'SEM': sem, 'deviation': sd, 'mean': mean, 'size': size, 'variance': variance }); if (me.hz != Infinity) { times.period = mean; times.cycle = mean * me.count; me.hz = 1 / mean; } } me.INIT_RUN_COUNT = runCount; me.emit('complete'); } } return !aborted; } // init sample/queue and begin enqueue(me.MIN_SAMPLE_SIZE); invoke(queue, { 'name': 'run', 'args': async, 'queued': true, 'onCycle': evaluate }); } /** * Runs the benchmark. * @member Benchmark * @param {Boolean} [async=false] Flag to run asynchronously. * @returns {Object} The benchmark instance. */ function run(async) { var me = this; function cycle() { var clocked, divisor, minTime, period, count = me.count, host = me.computing, times = me.times; // continue, if not aborted between cycles if (me.running) { me.cycles++; host.cycles++; host.count = count; try { clocked = clock(host); minTime = me.MIN_TIME; } catch(e) { me.abort(); me.error = e; me.emit('error'); } } // continue, if not errored if (me.running) { // time taken to complete last test cycle times.cycle = clocked; // seconds per operation period = times.period = clocked / count; // ops per second me.hz = 1 / period; // do we need to do another cycle? me.running = clocked < minTime; // avoid working our way up to this next time me.INIT_RUN_COUNT = count; if (me.running) { // tests may clock at 0 when INIT_RUN_COUNT is a small number, // to avoid that we set its count to something a bit higher if (!clocked && (divisor = CYCLE_DIVISORS[me.cycles]) != null) { count = Math.floor(4e6 / divisor); } // calculate how many more iterations it will take to achive the MIN_TIME if (count <= me.count) { count += Math.ceil((minTime - clocked) / period); } me.running = count != Infinity; } } // should we exit early? if (me.emit('cycle') === false) { me.abort(); } // figure out what to do next if (me.running) { me.count = count; call(me, cycle, async); } else { // fix TraceMonkey bug // http://bugzil.la/509069 if (window.Benchmark == Benchmark) { window.Benchmark = 1; window.Benchmark = Benchmark; } me.emit('complete'); } } // set running to false so reset() won't call abort() me.running = false; me.reset(); me.running = true; me.count = me.INIT_RUN_COUNT; me.times.start = +new Date; me.emit('start'); async = (async == null ? me.DEFAULT_ASYNC : async) && HAS_TIMEOUT_API; if (me.computing) { cycle(); } else { compute(me, async); } return me; } /*--------------------------------------------------------------------------*/ /** * Platform object containing browser name, version, and operating system. * @static * @member Benchmark * @type Object */ Benchmark.platform = (function() { var me = this, alpha = IN_JAVA ? 'a' : '\u03b1', beta = IN_JAVA ? 'b' : '\u03b2', description = [], doc = window.document || {}, nav = window.navigator || {}, ua = nav.userAgent || 'unknown platform', layout = /Gecko|Trident|WebKit/.exec(ua), data = { '6.1': 'Server 2008 R2 / 7', '6.0': 'Server 2008 / Vista', '5.2': 'Server 2003 / XP x64', '5.1': 'XP', '5.0': '2000', '4.0': 'NT', '4.9': 'ME' }, name = 'Avant Browser,Camino,Epiphany,Fennec,Flock,Galeon,GreenBrowser,iCab,Iron,K-Meleon,Konqueror,Lunascape,Maxthon,Minefield,Nook Browser,RockMelt,SeaMonkey,Sleipnir,SlimBrowser,Sunrise,Swiftfox,Opera Mini,Opera,Chrome,Firefox,IE,Safari', os = 'Android,Cygwin,SymbianOS,webOS[ /]\\d,Linux,Mac OS(?: X)?,Macintosh,Windows 98;,Windows ', product = 'BlackBerry\\s?\\d+,iP[ao]d,iPhone,Kindle,Nokia,Nook,PlayBook,Samsung,Xoom', version = isClassOf(window.opera, 'Opera') && opera.version(); function format(string) { // trim and conditionally capitalize return /^(?:webOS|i(?:OS|P))/.test(string = trim(string)) ? string : string.charAt(0).toUpperCase() + string.slice(1); } name = reduce(name.split(','), function(name, guess) { return name || RegExp(guess + '\\b', 'i').exec(ua) && guess; }); product = reduce(product.split(','), function(product, guess) { if (!product && (product = RegExp(guess + '[^ ();-]*', 'i').exec(ua))) { // correct character case and split by forward slash if ((product = String(product).replace(RegExp(guess = /\w+/.exec(guess), 'i'), guess).split('/'))[1]) { if (/[\d.]+/.test(product[0])) { version = version || product[1]; } else { product[0] += ' ' + product[1]; } } product = format(product[0].replace(/([a-z])(\d)/i, '$1 $2').split('-')[0]); } return product; }); os = reduce(os.split(','), function(os, guess) { if (!os && (os = RegExp(guess + '[^();/-]*').exec(ua))) { // platform tokens defined at // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx if (/^win/i.test(os) && (data = data[0/*opera fix*/,/[456]\.\d/.exec(os)])) { os = 'Windows ' + data; } // normalize iOS else if (/^iP/.test(product)) { name || (name = 'Safari'); os = 'iOS' + ((data = /\bOS ([\d_]+)/.exec(ua)) ? ' ' + data[1] : ''); } // cleanup os = String(os).replace(RegExp(guess = /\w+/.exec(guess), 'i'), guess) .replace(/\/(\d)/, ' $1').replace(/_/g, '.').replace(/x86\.64/g, 'x86_64') .replace('Macintosh', 'Mac OS').replace(/(OS X) Mach$/, '$1').split(' on ')[0]; } return os; }); // detect simulator if (/Simulator/i.exec(ua)) { product = (product ? product + ' ' : '') + 'Simulator'; } // detect non Firefox Gecko/Safari WebKit based browsers if (ua && (data = /^(?:Firefox|Safari|null)/.exec(name))) { if (name && !product && /[/,]/.test(ua.slice(ua.indexOf(data + '/') + 8))) { name = null; } if ((data = product || os) && !/^(?:iP|Lin|Mac|Win)/.test(data)) { name = /[a-z]+/i.exec(/^And/.test(os) && os || data) + ' Browser'; } } // detect non Opera versions if (!version) { version = reduce(['version', name, 'AdobeAIR', 'Firefox', 'NetFront'], function(version, guess) { return version || (RegExp(guess + '(?:-[\\d.]+/|[ /-])([^ ();/-]*)', 'i').exec(ua) || 0)[1] || null; }); } // detect server-side js if (me && isHostType(me, 'global')) { if (typeof exports == 'object' && exports) { if (me == window && typeof system == 'object' && (data = system)) { name = data.global == global ? 'Narwhal' : 'RingoJS'; os = data.os || null; } else if (typeof process == 'object' && (data = process)) { name = 'Node.js'; version = /[\d.]+/.exec(data.version)[0]; os = data.platform; } } else if (isClassOf(me.environment, 'Environment')) { name = 'Rhino'; } if (IN_JAVA && !os) { os = String(java.lang.System.getProperty('os.name')); } } // detect Adobe AIR else if (IN_AIR) { name = 'Adobe AIR'; os = runtime.flash.system.Capabilities.os; } // detect PhantomJS else if (isClassOf(data = window.phantom, 'RuntimeObject')) { name = 'PhantomJS'; version = (data = data.version) && (data.major + '.' + data.minor + '.' + data.patch); } // detect IE compatibility mode else if (typeof doc.documentMode == 'number' && (data = /Trident\/(\d+)/.exec(ua))) { version = [version, doc.documentMode]; version[1] = (data = +data[1] + 4) != version[1] ? (layout = null, description.push('running in IE ' + version[1] + ' mode'), data) : version[1]; version = name == 'IE' ? String(version[1].toFixed(1)) : version[0]; } // detect release phases if (version && (data = /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) || /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + nav.appMinorVersion))) { version = version.replace(RegExp(data + '\\+?$'), '') + (/^b/i.test(data) ? beta : alpha) + (/\d+\+?/.exec(data) || ''); } // detect Maxthon's unreliable version info if (/^Max/.test(name)) { version = version && version.replace(/\.[.\d]*/, '.x'); } // detect Firefox nightly else if (/^Min/.test(name)) { name = 'Firefox'; version = RegExp(alpha + '|' + beta + '|null').test(version) ? version : version + alpha; } // detect mobile else if (name && (!product || name == 'IE') && !/Bro/.test(name) && /Mobi/.test(ua)) { name += ' Mobile'; } // detect unspecified Chrome/Safari versions if (data = (/AppleWebKit\/(\d+(?:\.\d+)?)/.exec(ua) || 0)[1]) { if (/^And/.exec(os)) { data = data < 530 ? 1 : data < 532 ? 2 : data < 532.5 ? 3 : data < 533 ? 4 : data < 534.3 ? 5 : data < 534.7 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : '10'; layout = 'like Chrome'; } else { data = data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : '4'; layout = 'like Safari'; } layout += ' ' + (data += typeof data == 'number' ? '.x' : '+'); version = name == 'Safari' && (!version || parseInt(version) > 45) ? data : version; } // detect platform preview if (RegExp(alpha + '|' + beta).test(version) && typeof external == 'object' && !external) { layout = layout && !/like /.test(layout) ? 'rendered by ' + layout : layout; description.unshift('platform preview'); } // add layout engine if (layout && /Ado|Bro|Lun|Max|Pha|Sle/.test(name)) { description.push(layout); } // combine contextual information if (description.length) { description = ['(' + description.join('; ') + ')']; } // append product if (product && String(name).indexOf(product) < 0) { description.push('on ' + product); } // add browser/os architecture if (/\b(?:WOW|x|IA)64\b/.test(ua)) { os = os && os + (/64/.test(os) ? '' : ' x64'); if (name && (/WOW64/.test(ua) || /\w(?:86|32)$/.test(nav.cpuClass || nav.platform))) { description.unshift('x86'); } } return { 'version': name && version && description.unshift(version) && version, 'name': name && description.unshift(name) && name, 'os': name && (os = os && format(os)) && description.push(product ? '(' + os + ')' : 'on ' + os) && os, 'product': product, 'description': description.length ? description.join(' ') : ua, 'toString': function() { return this.description; } }; }()); /*--------------------------------------------------------------------------*/ extend(Benchmark, { /** * The version number. * @static * @member Benchmark * @type String */ 'version': '0.2.1', /** * The default options object copied by instances. * @static * @member Benchmark * @type Object */ 'options': { }, // generic Array#forEach/for-in 'each': each, // copy properties to another object 'extend': extend, // generic Array#filter 'filter': filter, // converts a number to a comma-separated string 'formatNumber': formatNumber, // xbrowser Object#hasOwnProperty 'hasKey': hasKey, // generic Array#indexOf 'indexOf': indexOf, // invokes a method on each item in an array 'invoke': invoke, // modifies a string using a template object 'interpolate': interpolate, // xbrowser Array.isArray 'isArray': isArray, // checks internal [[Class]] of an object 'isClassOf': isClassOf, // checks if an object's property is a non-primitive value 'isHostType': isHostType, // generic Array#join for arrays and objects 'join': join, // generic Array#map 'map': map, // no operation 'noop': noop, // retrieves a property value from each item in an array 'pluck': pluck, // generic Array#reduce 'reduce': reduce, // generic String#trim 'trim': trim }); /*--------------------------------------------------------------------------*/ // IE may ignore `toString` in a for-in loop Benchmark.prototype.toString = toString; extend(Benchmark.prototype, { /** * The delay between test cycles (secs). * @member Benchmark * @type Number */ 'CYCLE_DELAY': 0.005, /** * A flag to indicate methods will run asynchronously by default. * @member Benchmark * @type Boolean */ 'DEFAULT_ASYNC': false, /** * The default number of times to execute a test on a benchmark's first cycle. * @member Benchmark * @type Number */ 'INIT_RUN_COUNT': 5, /** * The maximum time a benchmark is allowed to run before finishing (secs). * @member Benchmark * @type Number */ 'MAX_TIME_ELAPSED': 5, /** * The minimum sample size required to perform statistical analysis. * @member Benchmark * @type Number */ 'MIN_SAMPLE_SIZE': 5, /** * The time needed to reduce the percent uncertainty of measurement to 1% (secs). * @member Benchmark * @type Number */ 'MIN_TIME': 0, /** * The number of times a test was executed. * @member Benchmark * @type Number */ 'count': 0, /** * A timestamp of when the benchmark was created. * @member Benchmark * @type Number */ 'created': 0, /** * The number of cycles performed while benchmarking. * @member Benchmark * @type Number */ 'cycles': 0, /** * The error object if the test failed. * @member Benchmark * @type Object|Null */ 'error': null, /** * The number of executions per second. * @member Benchmark * @type Number */ 'hz': 0, /** * A flag to indicate if the benchmark is aborted. * @member Benchmark * @type Boolean */ 'aborted': false, /** * A flag to indicate if the benchmark is running. * @member Benchmark * @type Boolean */ 'running': false, /** * Alias of [`Benchmark#addListener`](#Benchmark:addListener). * @member Benchmark, Benchmark.Suite */ 'on': addListener, /** * Compiled into the test and executed immediately **before** the test loop. * @member Benchmark * @type Function * @example * * var bench = new Benchmark({ * 'fn': function() { * a += 1; * }, * 'setup': function() { * // reset local var `a` at the beginning of each test cycle * a = 0; * } * }); * * // compiles into something like: * var a = 0; * var start = new Date; * while (count--) { * a += 1; * } * var end = new Date - start; */ 'setup': noop, /** * Compiled into the test and executed immediately **after** the test loop. * @member Benchmark * @type Function */ 'teardown': noop, /** * An object of stats including mean, margin or error, and standard deviation. * @member Benchmark * @type Object */ 'stats': { /** * The margin of error. * @member Benchmark#stats * @type Number */ 'ME': 0, /** * The relative margin of error (expressed as a percentage of the mean). * @member Benchmark#stats * @type Number */ 'RME': 0, /** * The standard error of the mean. * @member Benchmark#stats * @type Number */ 'SEM': 0, /** * The sample standard deviation. * @member Benchmark#stats * @type Number */ 'deviation': 0, /** * The sample arithmetic mean. * @member Benchmark#stats * @type Number */ 'mean': 0, /** * The sample size. * @member Benchmark#stats * @type Number */ 'size': 0, /** * The sample variance. * @member Benchmark#stats * @type Number */ 'variance': 0 }, /** * An object of timing data including cycle, elapsed, period, start, and stop. * @member Benchmark * @type Object */ 'times': { /** * The time taken to complete the last cycle (secs) * @member Benchmark#times * @type Number */ 'cycle': 0, /** * The time taken to complete the benchmark (secs). * @member Benchmark#times * @type Number */ 'elapsed': 0, /** * The time taken to execute the test once (secs). * @member Benchmark#times * @type Number */ 'period': 0, /** * A timestamp of when the benchmark started (ms). * @member Benchmark#times * @type Number */ 'start': 0, /** * A timestamp of when the benchmark finished (ms). * @member Benchmark#times * @type Number */ 'stop': 0 }, // aborts benchmark (does not record times) 'abort': abort, // registers a single listener 'addListener': addListener, // creates a new benchmark using the same test and options 'clone': clone, // compares benchmark's hertz with another 'compare': compare, // executes listeners of a specified type 'emit': emit, // removes all listeners of a specified type 'removeAllListeners': removeAllListeners, // removes a single listener 'removeListener': removeListener, // reset benchmark properties 'reset': reset, // runs the benchmark 'run': run }); /*--------------------------------------------------------------------------*/ /** * The default options object copied by instances. * @static * @member Benchmark.Suite * @type Object */ Suite.options = { }; /*--------------------------------------------------------------------------*/ extend(Suite.prototype, { /** * The number of benchmarks in the suite. * @member Benchmark.Suite * @type Number */ 'length': 0, /** * A flag to indicate if the suite is aborted. * @member Benchmark.Suite * @type Boolean */ 'aborted': false, /** * A flag to indicate if the suite is running. * @member Benchmark.Suite * @type Boolean */ 'running': false, /** * A bare-bones `Array#forEach` solution. * Callbacks may terminate the loop by explicitly returning `false`. * @member Benchmark.Suite * @param {Function} callback The function called per iteration. * @returns {Object} The suite iterated over. */ 'each': methodize(each), /** * A bare-bones `Array#indexOf` solution. * @member Benchmark.Suite * @param {Mixed} value The value to search for. * @returns {Number} The index of the matched value or `-1`. */ 'indexOf': methodize(indexOf), /** * Invokes a method on all benchmarks in the suite. * @member Benchmark.Suite * @param {String|Object} name The name of the method to invoke OR options object. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. * @returns {Array} A new array of values returned from each method invoked. */ 'invoke': methodize(invoke), /** * A bare-bones `Array#map` solution. * @member Benchmark.Suite * @param {Function} callback The function called per iteration. * @returns {Array} A new array of values returned by the callback. */ 'map': methodize(map), /** * Retrieves the value of a specified property from all benchmarks in the suite. * @member Benchmark.Suite * @param {String} property The property to pluck. * @returns {Array} A new array of property values. */ 'pluck': methodize(pluck), /** * A bare-bones `Array#reduce` solution. * @member Benchmark.Suite * @param {Function} callback The function called per iteration. * @param {Mixed} accumulator Initial value of the accumulator. * @returns {Mixed} The accumulator. */ 'reduce': methodize(reduce), // aborts all benchmarks in the suite 'abort': abortSuite, // adds a benchmark to the suite 'add': add, // registers a single listener 'addListener': addListener, // creates a new suite with cloned benchmarks 'clone': cloneSuite, // executes listeners of a specified type 'emit': emit, // creates a new suite of filtered benchmarks 'filter': filterSuite, // alias of addListener 'on': addListener, // removes all listeners of a specified type 'removeAllListeners': removeAllListeners, // removes a single listener 'removeListener': removeListener, // resets all benchmarks in the suite 'reset': resetSuite, // runs all benchmarks in the suite 'run': runSuite, // array methods 'concat': [].concat, 'join': [].join, 'pop': aloClean([].pop), 'push': [].push, 'reverse': [].reverse, 'shift': shift, 'slice': slice, 'sort': [].sort, 'splice': aloClean([].splice), 'unshift': [].unshift }); /*--------------------------------------------------------------------------*/ // expose Suite Benchmark.Suite = Suite; // expose Benchmark if (typeof exports == 'object' && exports && typeof global == 'object' && global) { window = global; if (typeof module == 'object' && module && module.exports == exports) { module.exports = Benchmark; } else { exports.Benchmark = Benchmark; } } else { window.Benchmark = Benchmark; } // trigger clock's lazy define early to avoid a security error if (IN_AIR) { clock({ 'fn': noop, 'count': 1 }); } // feature detect HAS_TIMEOUT_API = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout'); }(this));