diff --git a/test/benchmark.html b/test/benchmark.html new file mode 100644 index 0000000..70b5ffb --- /dev/null +++ b/test/benchmark.html @@ -0,0 +1,13 @@ + + + + mustache.js benchmark tests + + + + + + + + + \ No newline at end of file diff --git a/test/benchmark.js b/test/benchmark.js new file mode 100644 index 0000000..8c9fb3e --- /dev/null +++ b/test/benchmark.js @@ -0,0 +1,34 @@ +var tmpl = "This is the story of guys who work on a project\n" + + "called {{project}}. Their names were {{#people}}{{firstName}} and {{/people}}\n" + + "they both enjoyed working on {{project}}.\n\n" + + "{{#people}}\n" + + "{{>personPet}}\n" + + "{{/people}}"; +var partials = { + personPet: "{{firstName}} {{lastName}} {{#pet}} owned a {{species}}. Its name was {{name}}.{{/pet}}{{^pet}}didn't own a pet.{{/pet}}" +}; +var data = { + project: "Handlebars", + people: [ + { firstName: "Yehuda", lastName: "Katz" }, + { firstName: "Alan", lastName: "Johnson", pet: { species: "cat", name: "Luke" } } + ] +} + +$(function() { + var bench = new Benchmark('to_html', function() { + Mustache.to_html(tmpl, data, partials); + }); + bench.run(); + $('
' + bench.toString() + '
').appendTo(document.body); + + var bench = new Benchmark('compiled', function() { + this.compiled_tmpl(data); + }, { + onStart: function() { + this.compiled_tmpl = Mustache.compile(tmpl, partials); + } + }); + bench.run(); + $('
' + bench.toString() + '
').appendTo(document.body); +}); \ No newline at end of file diff --git a/test/benchmark/benchmark.js b/test/benchmark/benchmark.js new file mode 100644 index 0000000..06d66c5 --- /dev/null +++ b/test/benchmark/benchmark.js @@ -0,0 +1,2221 @@ +/*! + * 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)); \ No newline at end of file diff --git a/test/benchmark/nano.jar b/test/benchmark/nano.jar new file mode 100644 index 0000000..b577840 Binary files /dev/null and b/test/benchmark/nano.jar differ