From 85cc251adffa0ade78d3163882a79485692b5c25 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Thu, 27 Mar 2014 22:49:50 -0400 Subject: [PATCH 1/8] Added baseline tests for prototype method and function call invocation --- test/perf/perf-class-invoke-method.js | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/perf/perf-class-invoke-method.js b/test/perf/perf-class-invoke-method.js index bb4f24a..01f185e 100644 --- a/test/perf/perf-class-invoke-method.js +++ b/test/perf/perf-class-invoke-method.js @@ -105,3 +105,32 @@ common.test( function() // run the same test internally foo.testInternal(); + +// allows us to compare private method invocation times with method +// invocations on a conventional prototype (the increment of `i` is to +// ensure that the function is not optimized away) +( function() +{ + var p = function() {}; + p.prototype.foo = function() { i++ }; + var o = new p(); + + common.test( function() + { + var i = count; + while ( i-- ) o.foo(); + }, count, '[baseline] Invoke method on prototype' ); +} )(); + + +// compare with plain old function invocation +( function() +{ + var f = function() { i++ }; + common.test( function() + { + var i = count; + while ( i-- ) f(); + }, count, '[baseline] Invoke function' ); +} )(); + From a52fcfa1d91792ab5492148840d25af1f0633ac5 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 28 Mar 2014 23:51:39 -0400 Subject: [PATCH 2/8] Removed fallback check on each defineSecureProp call This check was originally added because IE8's implementation is broken. However, we already perform a test in the `can_define_prop` configuration, so this is not necessary (it seems to be a relic). --- lib/util.js | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/util.js b/lib/util.js index 8c3cf00..28f5223 100644 --- a/lib/util.js +++ b/lib/util.js @@ -562,8 +562,8 @@ exports.defineSecureProp( exports.getPropertyDescriptor, 'canTraverse', /** - * Appropriately returns defineSecureProp implementation to avoid check on each - * invocation + * Appropriately returns defineSecureProp implementation to avoid check on + * each invocation * * @return {function( Object, string, * )} */ @@ -584,26 +584,14 @@ function getDefineSecureProp() // uses ECMAScript 5's Object.defineProperty() method return function( obj, prop, value ) { - try + Object.defineProperty( obj, prop, { - Object.defineProperty( obj, prop, - { - value: value, + value: value, - enumerable: false, - writable: false, - configurable: false, - }); - } - catch ( e ) - { - // let's not have this happen again, as repeatedly throwing - // exceptions will do nothing but slow down the system - exports.definePropertyFallback( true ); - - // there's an error (ehem, IE8); fall back - fallback( obj, prop, value ); - } + enumerable: false, + writable: false, + configurable: false, + } ); }; } } From c76178516ed3645a4f4e271ffc729a5e82cd3139 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 29 Mar 2014 00:40:36 -0400 Subject: [PATCH 3/8] Various argument handling optimizations Permits more aggressive v8 optimization. --- lib/ClassBuilder.js | 12 +++-- lib/class.js | 112 +++++++++++++++++++++++++----------------- lib/class_abstract.js | 16 ++---- lib/class_final.js | 16 ++---- lib/interface.js | 7 +-- lib/util.js | 7 ++- 6 files changed, 94 insertions(+), 76 deletions(-) diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js index bd2e270..07048e0 100644 --- a/lib/ClassBuilder.js +++ b/lib/ClassBuilder.js @@ -299,9 +299,10 @@ exports.prototype.build = function extend( _, __ ) // ensure we'll be permitted to instantiate abstract classes for the base this._extending = true; - var args = Array.prototype.slice.call( arguments ), - props = args.pop() || {}, - base = args.pop() || exports.ClassBase, + var a = arguments, + an = a.length, + props = ( ( an > 0 ) ? a[ an - 1 ] : 0 ) || {}, + base = ( ( an > 1 ) ? a[ an - 2 ] : 0 ) || exports.ClassBase, prototype = this._getBase( base ), cname = '', autoa = false, @@ -508,7 +509,10 @@ exports.prototype.buildMembers = function buildMembers( { handlers[ name ] = function() { - var args = Array.prototype.slice.call( arguments ); + var args = [], + i = arguments.length; + + while ( i-- ) args[ i ] = arguments[ i ]; // invoke the custom handler with the original handler as // its last argument (which the custom handler may choose diff --git a/lib/class.js b/lib/class.js index 1c76898..65f8a41 100644 --- a/lib/class.js +++ b/lib/class.js @@ -64,19 +64,24 @@ var _nullf = function() { return null; } module.exports = function( namedef, def ) { var type = ( typeof namedef ), - result = null + result = null, + args = [], + i = arguments.length ; + // passing arguments object prohibits optimizations in v8 + while ( i-- ) args[ i ] = arguments[ i ]; + switch ( type ) { // anonymous class case 'object': - result = createAnonymousClass.apply( null, arguments ); + result = createAnonymousClass.apply( null, args ); break; // named class case 'string': - result = createNamedClass.apply( null, arguments ); + result = createNamedClass.apply( null, args ); break; default: @@ -99,10 +104,7 @@ module.exports = function( namedef, def ) * * @return {Function} extended class */ -module.exports.extend = function( baseordfn, dfn ) -{ - return extend.apply( this, arguments ); -}; +module.exports.extend = extend; /** @@ -138,11 +140,11 @@ module.exports.implement = function( interfaces ) */ module.exports.use = function( traits ) { + var args = [], i = arguments.length; + while( i-- ) args[ i ] = arguments[ i ]; + // consume traits onto an empty base - return createUse( - _nullf, - Array.prototype.slice.call( arguments ) - ); + return createUse( _nullf, args ); }; @@ -292,7 +294,10 @@ function createStaging( cname ) return { extend: function() { - var args = Array.prototype.slice.apply( arguments ); + var args = [], + i = arguments.length; + + while ( i-- ) args[ i ] = arguments[ i ]; // extend() takes a maximum of two arguments. If only one // argument is provided, then it is to be the class definition. @@ -308,21 +313,24 @@ function createStaging( cname ) implement: function() { + var args = [], + i = arguments.length; + + while ( i-- ) args[ i ] = arguments[ i ]; + // implement on empty base, providing the class name to be used once // extended - return createImplement( - null, - Array.prototype.slice.call( arguments ), - cname - ); + return createImplement( null, args, cname ); }, use: function() { - return createUse( - _nullf, - Array.prototype.slice.call( arguments ) - ); + var args = [], + i = arguments.length; + + while ( i-- ) args[ i ] = arguments[ i ]; + + return createUse( _nullf, args ); }, }; } @@ -349,14 +357,14 @@ function createImplement( base, ifaces, cname ) var partial = { extend: function() { - var args = Array.prototype.slice.call( arguments ), - def = args.pop(), - ext_base = args.pop() + var an = arguments.length, + def = arguments[ an - 1 ], + ext_base = ( an > 1 ) ? arguments[ an - 2 ] : null ; // if any arguments remain, then they likely misunderstood what this // method does - if ( args.length > 0 ) + if ( an > 2 ) { throw Error( "Expecting no more than two arguments for extend()" @@ -394,7 +402,12 @@ function createImplement( base, ifaces, cname ) // much more performant (it creates a subtype before mixing in) use: function() { - var traits = Array.prototype.slice.call( arguments ); + var traits = [], + i = arguments.length; + + // passing arguments object prohibits optimizations in v8 + while ( i-- ) traits[ i ] = arguments[ i ]; + return createUse( function() { return partial.__createBase(); }, traits @@ -468,9 +481,9 @@ function createUse( basef, traits, nonbase ) // given during the extend operation partial.extend = function() { - var args = Array.prototype.slice.call( arguments ), - dfn = args.pop(), - ext_base = args.pop(), + var an = arguments.length, + dfn = arguments[ an - 1 ], + ext_base = ( an > 1 ) ? arguments[ an - 2 ] : null, base = basef(); // extend the mixed class, which ensures that all super references @@ -485,12 +498,17 @@ function createUse( basef, traits, nonbase ) // call simply to mix in another trait partial.use = function() { + var args = [], + i = arguments.length; + + while ( i-- ) args[ i ] = arguments[ i ]; + return createUse( function() { return partial.__createBase(); }, - Array.prototype.slice.call( arguments ), + args, nonbase ); }; @@ -557,8 +575,14 @@ function createMixedClass( base, traits ) */ function extend( _, __ ) { + var args = [], + i = arguments.length; + + // passing arguments object prohibits optimizations in v8 + while ( i-- ) args[ i ] = arguments[ i ]; + // set up the new class - var new_class = class_builder.build.apply( class_builder, arguments ); + var new_class = class_builder.build.apply( class_builder, args ); // set up some additional convenience props setupProps( new_class ); @@ -584,10 +608,9 @@ function extend( _, __ ) */ var implement = function( baseobj, interfaces ) { - var args = Array.prototype.slice.call( arguments ), + var an = arguments.length, dest = {}, - base = args.pop(), - len = args.length, + base = arguments[ an - 1 ], arg = null, implemented = [], @@ -595,9 +618,9 @@ var implement = function( baseobj, interfaces ) ; // add each of the interfaces - for ( var i = 0; i < len; i++ ) + for ( var i = 0; i < ( an - 1 ); i++ ) { - arg = args[ i ]; + arg = arguments[ i ]; // copy all interface methods to the class (does not yet deep copy) util.propParse( arg.prototype, { @@ -678,10 +701,10 @@ function attachImplement( func ) { util.defineSecureProp( func, 'implement', function() { - return createImplement( - func, - Array.prototype.slice.call( arguments ) - ); + var args = [], i = arguments.length; + while( i-- ) args[ i ] = arguments[ i ]; + + return createImplement( func, args ); }); } @@ -699,11 +722,10 @@ function attachUse( func ) { util.defineSecureProp( func, 'use', function() { - return createUse( - function() { return func; }, - Array.prototype.slice.call( arguments ), - true - ); + var args = [], i = arguments.length; + while( i-- ) args[ i ] = arguments[ i ]; + + return createUse( function() { return func; }, args, true ); } ); } diff --git a/lib/class_abstract.js b/lib/class_abstract.js index 0441904..deb920d 100644 --- a/lib/class_abstract.js +++ b/lib/class_abstract.js @@ -33,7 +33,7 @@ var Class = require( __dirname + '/class' ); */ module.exports = exports = function() { - markAbstract( arguments ); + markAbstract( arguments[ arguments.length - 1 ] ); // forward everything to Class var result = Class.apply( this, arguments ); @@ -56,7 +56,7 @@ module.exports = exports = function() */ exports.extend = function() { - markAbstract( arguments ); + markAbstract( arguments[ arguments.length - 1 ] ); return Class.extend.apply( this, arguments ); }; @@ -92,18 +92,12 @@ exports.implement = function() /** * Causes a definition to be flagged as abstract * - * This function assumes the last argument to be the definition, which is the - * common case, and modifies the object referenced by that argument. - * - * @param {Arguments} args arguments to parse + * @param {*} dfn suspected definition object * * @return {undefined} */ -function markAbstract( args ) +function markAbstract( dfn ) { - // the last argument _should_ be the definition - var dfn = args[ args.length - 1 ]; - if ( typeof dfn === 'object' ) { // mark as abstract @@ -141,7 +135,7 @@ function abstractOverride( obj ) // wrap extend, applying the abstract flag obj.extend = function() { - markAbstract( arguments ); + markAbstract( arguments[ arguments.length - 1 ] ); return extend.apply( this, arguments ); }; diff --git a/lib/class_final.js b/lib/class_final.js index e00dec6..c7fabd1 100644 --- a/lib/class_final.js +++ b/lib/class_final.js @@ -29,7 +29,7 @@ var Class = require( __dirname + '/class' ); */ exports = module.exports = function() { - markFinal( arguments ); + markFinal( arguments[ arguments.length - 1 ] ); // forward everything to Class var result = Class.apply( this, arguments ); @@ -50,7 +50,7 @@ exports = module.exports = function() */ exports.extend = function() { - markFinal( arguments ); + markFinal( arguments[ arguments.length - 1 ] ); return Class.extend.apply( this, arguments ); }; @@ -58,18 +58,12 @@ exports.extend = function() /** * Causes a definition to be flagged as final * - * This function assumes the last argument to be the definition, which is the - * common case, and modifies the object referenced by that argument. - * - * @param {!Arguments} args arguments to parse + * @param {!Arguments} dfn suspected definition object * * @return {undefined} */ -function markFinal( args ) +function markFinal( dfn ) { - // the last argument _should_ be the definition - var dfn = args[ args.length - 1 ]; - if ( typeof dfn === 'object' ) { // mark as abstract @@ -92,7 +86,7 @@ function finalOverride( obj ) // wrap extend, applying the abstract flag obj.extend = function() { - markFinal( arguments ); + markFinal( arguments[ arguments.length - 1 ] ); return extend.apply( this, arguments ); }; } diff --git a/lib/interface.js b/lib/interface.js index 474c568..209beec 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -184,9 +184,10 @@ var extend = ( function( extending ) // ensure we'll be permitted to instantiate interfaces for the base extending = true; - var args = Array.prototype.slice.call( arguments ), - props = args.pop() || {}, - base = args.pop() || Interface, + var a = arguments, + an = a.length, + props = ( ( an > 0 ) ? a[ an - 1 ] : 0 ) || {}, + base = ( ( an > 1 ) ? a[ an - 2 ] : 0 ) || Interface, prototype = new base(), iname = '', diff --git a/lib/util.js b/lib/util.js index 28f5223..cd4367a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -399,7 +399,10 @@ function verifyAbstractNames( name, params ) */ exports.createAbstractMethod = function( def ) { - var definition = Array.prototype.slice.call( arguments ); + var dfn = [], + i = arguments.length; + + while ( i-- ) dfn[ i ] = arguments[ i ]; var method = function() { @@ -407,7 +410,7 @@ exports.createAbstractMethod = function( def ) }; exports.defineSecureProp( method, 'abstractFlag', true ); - exports.defineSecureProp( method, 'definition', definition ); + exports.defineSecureProp( method, 'definition', dfn ); exports.defineSecureProp( method, '__length', arguments.length ); return method; From d23f34da4be5fc01cf65508c238246cd29136565 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 29 Mar 2014 00:57:22 -0400 Subject: [PATCH 4/8] Removed try/catch from Interface.extend Permits more aggressive optimization. --- lib/interface.js | 92 +++++++++++++++++++++++++++--------------------- lib/util.js | 29 +++++++++++---- 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/lib/interface.js b/lib/interface.js index 209beec..a3b2227 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -177,6 +177,23 @@ function createNamedInterface( name, def ) } +/** + * Augment an exception with interface name and then throw + * + * @param {string} iname interface name or empty string + * @param {Error} e exception to augment + */ +function _ithrow( iname, e ) +{ + // alter the message to include our name + e.message = "Failed to define interface " + + ( ( iname ) ? iname : '(anonymous)' ) + ": " + e.message + ; + + throw e; +} + + var extend = ( function( extending ) { return function extend() @@ -211,50 +228,43 @@ var extend = ( function( extending ) var new_interface = createInterface( iname ); - try - { - util.propParse( props, { - assumeAbstract: true, + util.propParse( props, { + assumeAbstract: true, - property: function() + // override default exceptions from parser errors + _throw: function( e ) + { + _ithrow( iname, e ); + }, + + property: function() + { + // should never get to this point because of assumeAbstract + _ithrow( iname, TypeError( "Unexpected internal error" ) ); + }, + + getset: function() + { + // should never get to this point because of assumeAbstract + _ithrow( iname, TypeError( "Unexpected internal error" ) ); + }, + + method: function( name, value, is_abstract, keywords ) + { + // all members must be public + if ( keywords[ 'protected' ] || keywords[ 'private' ] ) { - // should never get to this point because of assumeAbstract - throw TypeError( 'Unexpected internal error' ); - }, + _ithrow( iname, TypeError( + "Member " + name + " must be public" + ) ); + } - getset: function() - { - // should never get to this point because of assumeAbstract - throw TypeError( 'Unexpected internal error' ); - }, - - method: function( name, value, is_abstract, keywords ) - { - // all members must be public - if ( keywords[ 'protected' ] || keywords[ 'private' ] ) - { - throw TypeError( - iname + " member " + name + " must be public" - ); - } - - member_builder.buildMethod( - members, null, name, value, keywords, - null, 0, {}, vstate - ); - }, - } ); - } - catch ( e ) - { - // alter the message to include our name - e.message = "Failed to define interface " + - ( ( iname ) ? iname : '(anonymous)' ) + ": " + e.message - ; - - // re-throw - throw e; - } + member_builder.buildMethod( + members, null, name, value, keywords, + null, 0, {}, vstate + ); + }, + } ); attachExtend( new_interface ); attachStringMethod( new_interface, iname ); diff --git a/lib/util.js b/lib/util.js index cd4367a..6c35ba7 100644 --- a/lib/util.js +++ b/lib/util.js @@ -246,6 +246,19 @@ exports.copyTo = function( dest, src, deep ) }; +/** + * Throw an exception + * + * Yes, this function has purpose; see where it's used. + * + * @param {Error} e exception to throw + */ +function _throw( e ) +{ + throw e; +} + + /** * Parses object properties to determine how they should be interpreted in an * Object Oriented manner @@ -268,6 +281,8 @@ exports.propParse = function( data, options, context ) callbackGetSet = options.getset || fvoid, keywordParser = options.keywordParser || propParseKeywords, + throwf = options._throw || _throw, + hasOwn = Object.prototype.hasOwnProperty, parse_data = {}, @@ -315,12 +330,12 @@ exports.propParse = function( data, options, context ) if ( !( value instanceof Array ) ) { - throw TypeError( + throwf( TypeError( "Missing parameter list for abstract method: " + name - ); + ) ); } - verifyAbstractNames( name, value ); + verifyAbstractNames( throwf, name, value ); value = exports.createAbstractMethod.apply( this, value ); } @@ -363,22 +378,24 @@ exports.propParse = function( data, options, context ) * In the future, we may add additional functionality, so it's important to * restrict this as much as possible for the time being. * + * @param {function(Error)} throwf function to call with error + * * @param {string} name name of abstract member (for error) * @param {Object} params parameter list to check * * @return {undefined} */ -function verifyAbstractNames( name, params ) +function verifyAbstractNames( throwf, name, params ) { var i = params.length; while ( i-- ) { if ( params[ i ].match( /^[a-z_][a-z0-9_]*$/i ) === null ) { - throw SyntaxError( + throwf( SyntaxError( "Member " + name + " contains invalid parameter '" + params[ i ] + "'" - ); + ) ); } } } From 58ee52ad4a0ebf51f9add9b99188968268ed6f8e Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 29 Mar 2014 00:59:42 -0400 Subject: [PATCH 5/8] Removed unnecessary try/catch from isInstanceOf Was just being lazy. --- lib/ClassBuilder.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js index 07048e0..caae2a8 100644 --- a/lib/ClassBuilder.js +++ b/lib/ClassBuilder.js @@ -244,16 +244,14 @@ exports.isInstanceOf = function( type, instance ) return false; } - try + // check prototype chain (will throw an error if type is not a + // constructor (function) + if ( ( typeof type === 'function' ) + && ( instance instanceof type ) + ) { - // check prototype chain (will throw an error if type is not a - // constructor (function) - if ( instance instanceof type ) - { - return true; - } + return true; } - catch ( e ) {} // if no metadata is available, then our remaining checks cannot be // performed From e85a7653e8ca1da1bd55ded062916105a5ed1ab8 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 29 Mar 2014 11:16:14 -0400 Subject: [PATCH 6/8] Generic performance test output Styled for display to user as the tests are running, but data are written to perf.out for additional processing. You can style the perf.out file cleanly using: $ column -ts\| perf.out --- .gitignore | 4 ++++ Makefile.am | 5 ++--- configure.ac | 7 ++++--- test/perf/common.js | 4 +--- test/perf/runner.in | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100755 test/perf/runner.in diff --git a/.gitignore b/.gitignore index f0d5048..e96f874 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ ChangeLog # autotools- and configure-generated test/runner +test/perf/runner aclocal.m4 Makefile.in Makefile @@ -17,6 +18,9 @@ Makefile configure config.* +# script output +perf.out + # should be added using autoreconf -i INSTALL tools/install-sh diff --git a/Makefile.am b/Makefile.am index 752c710..fadc540 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,10 +112,9 @@ test: check check: $(src_tests) test-suite # performance tests -perf: @PERF_TESTS@ -perf-%.js: FORCE +perf: if HAS_NODE - @$(NODE) $@ + @$(path_test)/perf/runner @PERF_TESTS@ else @echo "Node.js must be installed in order to run performance tests" @exit 1 diff --git a/configure.ac b/configure.ac index 3bc5bb4..783e0a4 100644 --- a/configure.ac +++ b/configure.ac @@ -87,7 +87,8 @@ PERF_TESTS=$( find test/perf -name 'perf-*.js' | tr '\n' ' ' ) AC_SUBST(PERF_TESTS) AS_IF([test "$PERF_TESTS"], [AC_MSG_RESULT(ok)], [AC_MSG_WARN(none found)]) -AC_CONFIG_FILES( - [Makefile doc/Makefile package.json lib/version.js test/runner], - [chmod +x test/runner]) +AC_CONFIG_FILES([Makefile doc/Makefile package.json lib/version.js]) +AC_CONFIG_FILES([test/runner], [chmod +x test/runner]) +AC_CONFIG_FILES([test/perf/runner], [chmod +x test/perf/runner]) + AC_OUTPUT diff --git a/test/perf/common.js b/test/perf/common.js index 324f05d..13c1d46 100644 --- a/test/perf/common.js +++ b/test/perf/common.js @@ -89,8 +89,6 @@ exports.report = function( count, desc ) pers = ( total / count ).toFixed( 10 ) ; - console.log( total + "s (x" + count + " = " + pers + "s each)" + - ( ( desc ) ? ( ': ' + desc ) : '' ) - ); + console.log( "%s|%s|%s|%s", desc, count, pers, total ); }; diff --git a/test/perf/runner.in b/test/perf/runner.in new file mode 100755 index 0000000..1fe846f --- /dev/null +++ b/test/perf/runner.in @@ -0,0 +1,36 @@ +#!/bin/sh +# +# Copyright (C) 2014 Mike Gerwitz +# +# This file is part of GNU ease.js. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# If you want formatted output, see :/tools/perf2html, or run this command: +# $ column -ts\| perf.out +# # + +rawout=perf.out +>"$rawout" + +for f in "$@"; do + @NODE@ "$f" || exit $? +done \ + | tee -a "$rawout" \ + | awk -F\| ' + # format for display as the tests are running + { printf "%s (x%s = %ss each): %s\n", $4, $2, $3, $1 } + ' + +echo "Raw data written to $rawout" >&2 From 1079630bd4d0e7f63ff78faa8adbd61d8dc3a51e Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 29 Mar 2014 20:13:26 -0400 Subject: [PATCH 7/8] Can now build multiple performance logs `make perf` will build, by default, perf.log, but you may also build perf.*; for example: $ make perf.1 # make some changes $ make perf.2 This allows comparing changes easily. --- .gitignore | 2 +- Makefile.am | 5 +++-- test/perf/runner.in | 12 +++--------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index e96f874..7404829 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ configure config.* # script output -perf.out +perf.* # should be added using autoreconf -i INSTALL diff --git a/Makefile.am b/Makefile.am index fadc540..d0b5796 100644 --- a/Makefile.am +++ b/Makefile.am @@ -112,9 +112,10 @@ test: check check: $(src_tests) test-suite # performance tests -perf: +perf: perf.log +perf.%: FORCE if HAS_NODE - @$(path_test)/perf/runner @PERF_TESTS@ + @$(path_test)/perf/runner @PERF_TESTS@ > "$@" else @echo "Node.js must be installed in order to run performance tests" @exit 1 diff --git a/test/perf/runner.in b/test/perf/runner.in index 1fe846f..ce046e2 100755 --- a/test/perf/runner.in +++ b/test/perf/runner.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Copyright (C) 2014 Mike Gerwitz # @@ -21,16 +21,10 @@ # $ column -ts\| perf.out # # -rawout=perf.out ->"$rawout" - for f in "$@"; do @NODE@ "$f" || exit $? done \ - | tee -a "$rawout" \ - | awk -F\| ' + | tee >( awk -F\| ' # format for display as the tests are running { printf "%s (x%s = %ss each): %s\n", $4, $2, $3, $1 } - ' - -echo "Raw data written to $rawout" >&2 + ' >&2 ) From b5ae607096af24a6706f73598175762fd1969ee6 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sun, 30 Mar 2014 00:12:19 -0400 Subject: [PATCH 8/8] Began performance test case result HTML generation This will eventually yield much more useful interactive output. --- Makefile.am | 5 ++- tools/perf2html | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100755 tools/perf2html diff --git a/Makefile.am b/Makefile.am index d0b5796..3a4f291 100644 --- a/Makefile.am +++ b/Makefile.am @@ -111,7 +111,10 @@ html-single: test: check check: $(src_tests) test-suite -# performance tests +# performance tests (order matters here) +perf-html: perf.log.html +perf.%.html: perf.% + sort "$<" | $(path_tools)/perf2html -F\| > "$@" perf: perf.log perf.%: FORCE if HAS_NODE diff --git a/tools/perf2html b/tools/perf2html new file mode 100755 index 0000000..842bb72 --- /dev/null +++ b/tools/perf2html @@ -0,0 +1,97 @@ +#!/usr/bin/awk -f +# Renders performance test output +# +# Copyright (C) 2014 Mike Gerwitz +# +# This file is part of GNU ease.js. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# If you want more modest output, consider running this command instead: +# $ column -ts\| perf.out +# # + +function styleout( i ) { + value = $i + + # slow tests should reduce the number of iterations to eat up less time + if ( ( i == COL_TOTAL ) && ( value > 0.05 ) ) { + class = "slow" + if ( value > 0.1 ) + class = "very " class + + return "" value "" + } + else + return value +} + + +BEGIN { + # column constants + COL_DESC = 1 + COL_COUNT = 2 + COL_SINGLE = 3 + COL_TOTAL = 4 + + # running time tally + runtime = 0.00 + + # header + print "" \ + "" \ + "" \ + "GNU ease.js Performance Test Results" \ + "" \ + "" \ + "" \ + "

GNU ease.js Performance Test Results

" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" \ + "" +} + +# format row of output (desc, count, time, total) +{ + runtime += $COL_TOTAL + + printf "" + for ( i = 1; i <= NF; i++ ) + { + printf "", styleout( i ) + } + printf "\n" +} + +END { + # footer + print "" \ + "
DescriptionIterationsSeconds/iterTotal (s)
%s
" \ + "

Total running time: " runtime " seconds

" \ + "" \ + "" +}