time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

timeit.js (5897B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2018 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 'use strict';
     20 
     21 // MODULES //
     22 
     23 var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
     24 var isFunction = require( '@stdlib/assert/is-function' );
     25 var isArray = require( '@stdlib/assert/is-array' );
     26 var copy = require( './../../copy' );
     27 var cwd = require( '@stdlib/process/cwd' );
     28 var nextTick = require( './../../next-tick' );
     29 var defaults = require( './defaults.json' );
     30 var validate = require( './validate.js' );
     31 var evaluate = require( './vm_evaluate.js' );
     32 var transform = require( './transform.js' );
     33 
     34 
     35 // VARIABLES //
     36 
     37 var FILENAME = 'timeit.js';
     38 var MIN_TIME = 0.1; // seconds
     39 var ITERATIONS = 10; // 10^1
     40 var MAX_ITERATIONS = 10000000000; // 10^10
     41 
     42 
     43 // MAIN //
     44 
     45 /**
     46 * Times a snippet.
     47 *
     48 * @param {string} code - snippet to time
     49 * @param {Options} [options] - function options
     50 * @param {string} [options.before=""] - setup code
     51 * @param {string} [options.after=""] - cleanup code
     52 * @param {(PositiveInteger|null)} [options.iterations=1e6] - number of iterations
     53 * @param {PositiveInteger} [options.repeats=3] - number of repeats
     54 * @param {boolean} [options.asynchronous=false] - boolean indicating whether a snippet is asynchronous
     55 * @param {Callback} clbk - callback to invoke upon completion
     56 * @throws {TypeError} first argument must be a string
     57 * @throws {TypeError} options argument must be an object
     58 * @throws {TypeError} must provide valid options
     59 * @throws {TypeError} callback argument must be a function
     60 * @returns {void}
     61 *
     62 * @example
     63 * var code = '';
     64 * code += 'var x = Math.pow( Math.random(), 3 );';
     65 * code += 'if ( x !== x ) {';
     66 * code += 'throw new Error( \'Something went wrong.\' );';
     67 * code += '}';
     68 *
     69 * timeit( code, done );
     70 *
     71 * function done( error, results ) {
     72 *     if ( error ) {
     73 *         throw error;
     74 *     }
     75 *     console.dir( results );
     76 * }
     77 */
     78 function timeit( code, options, clbk ) {
     79 	var results;
     80 	var opts;
     81 	var dir;
     82 	var err;
     83 	var idx;
     84 	var cb;
     85 
     86 	if ( !isString( code ) ) {
     87 		throw new TypeError( 'invalid argument. First argument must be a primitive string. Value: `' + code + '`.' );
     88 	}
     89 	opts = copy( defaults );
     90 	if ( arguments.length === 2 ) {
     91 		cb = options;
     92 	} else {
     93 		cb = clbk;
     94 		err = validate( opts, options );
     95 		if ( err ) {
     96 			throw err;
     97 		}
     98 	}
     99 	if ( !isFunction( cb ) ) {
    100 		throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + cb + '`.' );
    101 	}
    102 	results = new Array( opts.repeats );
    103 	dir = cwd();
    104 	idx = 0;
    105 
    106 	// Pretest to check for early returns and/or errors...
    107 	try {
    108 		evaluate( code, opts, FILENAME, dir, onTest );
    109 	} catch ( error ) {
    110 		err = new Error( 'evaluation error. Encountered an error when evaluating snippet. '+error.message );
    111 		return done( err );
    112 	}
    113 
    114 	/**
    115 	* Evaluates a code snippet on the next turn of the event loop. Waiting until the next turn avoids the current turn being bogged down by a long running queue.
    116 	*
    117 	* @private
    118 	* @param {Callback} clbk - callback
    119 	*/
    120 	function next( clbk ) {
    121 		nextTick( onTick );
    122 
    123 		/**
    124 		* Callback invoked upon next turn of event loop.
    125 		*
    126 		* @private
    127 		*/
    128 		function onTick() {
    129 			evaluate( code, opts, FILENAME, dir, clbk );
    130 		}
    131 	}
    132 
    133 	/**
    134 	* Callback invoked after completing pretest.
    135 	*
    136 	* @private
    137 	* @param {(Error|null)} error - error object
    138 	* @param {NonNegativeIntegerArray} time - results
    139 	* @returns {void}
    140 	*/
    141 	function onTest( error, time ) {
    142 		if ( error ) {
    143 			return done( error );
    144 		}
    145 		if ( !isArray( time ) || time.length !== 2 ) {
    146 			// This should only happen if someone is a bad actor and attempts to call the `done` callback without providing timing results.
    147 			error = new Error( 'evaluation error. Did not receive timing results.' );
    148 			return done( error );
    149 		}
    150 		if ( opts.iterations === null ) {
    151 			opts.iterations = ITERATIONS;
    152 			return next( onRun );
    153 		}
    154 		// Begin timing the snippet...
    155 		return next( onFinish );
    156 	}
    157 
    158 	/**
    159 	* Callback invoked upon running a pre-run to determine the number of iterations.
    160 	*
    161 	* @private
    162 	* @param {(Error|null)} error - error object
    163 	* @param {NonNegativeIntegerArray} time - results
    164 	* @returns {void}
    165 	*/
    166 	function onRun( error, time ) {
    167 		var t;
    168 		if ( error ) {
    169 			return done( error );
    170 		}
    171 		t = time[ 0 ] + ( time[ 1 ]/1e9 );
    172 		if (
    173 			t < MIN_TIME &&
    174 			opts.iterations < MAX_ITERATIONS
    175 		) {
    176 			opts.iterations *= 10;
    177 			return next( onRun );
    178 		}
    179 		// Begin timing the snippet...
    180 		return next( onFinish );
    181 	}
    182 
    183 	/**
    184 	* Callback invoked upon executing code.
    185 	*
    186 	* @private
    187 	* @param {(Error|null)} error - error object
    188 	* @param {NonNegativeIntegerArray} time - results
    189 	* @returns {void}
    190 	*/
    191 	function onFinish( error, time ) {
    192 		if ( error ) {
    193 			return done( error );
    194 		}
    195 		results[ idx ] = time;
    196 		idx += 1;
    197 		if ( idx < opts.repeats ) {
    198 			return next( onFinish );
    199 		}
    200 		done( null, results );
    201 	}
    202 
    203 	/**
    204 	* Callback invoked upon completion.
    205 	*
    206 	* @private
    207 	* @param {(Error|null)} error - error object
    208 	* @param {ArrayArray} results - raw results
    209 	*/
    210 	function done( error, results ) {
    211 		var out;
    212 		if ( !error ) {
    213 			out = transform( results, opts.iterations );
    214 		}
    215 		// Avoid releasing the zalgo:
    216 		nextTick( onTick );
    217 
    218 		/**
    219 		* Callback invoked upon the next tick.
    220 		*
    221 		* @private
    222 		* @returns {void}
    223 		*/
    224 		function onTick() {
    225 			if ( error ) {
    226 				return cb( error );
    227 			}
    228 			cb( null, out );
    229 		}
    230 	}
    231 }
    232 
    233 
    234 // EXPORTS //
    235 
    236 module.exports = timeit;