1
0
Fork 0

Miscellaneous performance enhancements

These are the beginning of some smaller performance optimizations brought on
by the v8 profiler. This includes removal or movement of over-reaching
try/catch blocks and more disciplined argument handling, neither of which
can be compiled into machine code (permanently, at least). This also removes
some unneeded code, adds some baseline performance test cases, and begins
generic performance test output and HTML generation which will be used in
the future for more detailed analysis.

This is just a starting point; there's more to come, guided by profiling.
The trait implementation needs some love and, since its development is not
yet complete, that will be optimized in the near future. Further, there are
additional optimizations that can be made when ease.js recognizes that
certain visibility layers are unneeded, allowing it to create more
lightweight classes.

Performance enhancements will also introduce the ability to generate a
``compiled'' class, which will generate a prototype that can be immediately
run without the overhead of processing keywords, etc. This will also have
the benefit of generating code that can be understood by static analysis
tools and, consequently, optimizers.

All in good time.
newmaster
Mike Gerwitz 2014-04-17 00:01:40 -04:00
commit 86a4703a1c
No known key found for this signature in database
GPG Key ID: F22BB8158EE30EAB
13 changed files with 354 additions and 161 deletions

4
.gitignore vendored
View File

@ -10,6 +10,7 @@ ChangeLog
# autotools- and configure-generated # autotools- and configure-generated
test/runner test/runner
test/perf/runner
aclocal.m4 aclocal.m4
Makefile.in Makefile.in
Makefile Makefile
@ -17,6 +18,9 @@ Makefile
configure configure
config.* config.*
# script output
perf.*
# should be added using autoreconf -i # should be added using autoreconf -i
INSTALL INSTALL
tools/install-sh tools/install-sh

View File

@ -111,11 +111,14 @@ html-single:
test: check test: check
check: $(src_tests) test-suite check: $(src_tests) test-suite
# performance tests # performance tests (order matters here)
perf: @PERF_TESTS@ perf-html: perf.log.html
perf-%.js: FORCE perf.%.html: perf.%
sort "$<" | $(path_tools)/perf2html -F\| > "$@"
perf: perf.log
perf.%: FORCE
if HAS_NODE if HAS_NODE
@$(NODE) $@ @$(path_test)/perf/runner @PERF_TESTS@ > "$@"
else else
@echo "Node.js must be installed in order to run performance tests" @echo "Node.js must be installed in order to run performance tests"
@exit 1 @exit 1

View File

@ -87,7 +87,8 @@ PERF_TESTS=$( find test/perf -name 'perf-*.js' | tr '\n' ' ' )
AC_SUBST(PERF_TESTS) AC_SUBST(PERF_TESTS)
AS_IF([test "$PERF_TESTS"], [AC_MSG_RESULT(ok)], [AC_MSG_WARN(none found)]) AS_IF([test "$PERF_TESTS"], [AC_MSG_RESULT(ok)], [AC_MSG_WARN(none found)])
AC_CONFIG_FILES( AC_CONFIG_FILES([Makefile doc/Makefile package.json lib/version.js])
[Makefile doc/Makefile package.json lib/version.js test/runner], AC_CONFIG_FILES([test/runner], [chmod +x test/runner])
[chmod +x test/runner]) AC_CONFIG_FILES([test/perf/runner], [chmod +x test/perf/runner])
AC_OUTPUT AC_OUTPUT

View File

@ -244,16 +244,14 @@ exports.isInstanceOf = function( type, instance )
return false; 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 return true;
// constructor (function)
if ( instance instanceof type )
{
return true;
}
} }
catch ( e ) {}
// if no metadata is available, then our remaining checks cannot be // if no metadata is available, then our remaining checks cannot be
// performed // performed
@ -299,9 +297,10 @@ exports.prototype.build = function extend( _, __ )
// ensure we'll be permitted to instantiate abstract classes for the base // ensure we'll be permitted to instantiate abstract classes for the base
this._extending = true; this._extending = true;
var args = Array.prototype.slice.call( arguments ), var a = arguments,
props = args.pop() || {}, an = a.length,
base = args.pop() || exports.ClassBase, props = ( ( an > 0 ) ? a[ an - 1 ] : 0 ) || {},
base = ( ( an > 1 ) ? a[ an - 2 ] : 0 ) || exports.ClassBase,
prototype = this._getBase( base ), prototype = this._getBase( base ),
cname = '', cname = '',
autoa = false, autoa = false,
@ -508,7 +507,10 @@ exports.prototype.buildMembers = function buildMembers(
{ {
handlers[ name ] = function() 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 // invoke the custom handler with the original handler as
// its last argument (which the custom handler may choose // its last argument (which the custom handler may choose

View File

@ -64,19 +64,24 @@ var _nullf = function() { return null; }
module.exports = function( namedef, def ) module.exports = function( namedef, def )
{ {
var type = ( typeof namedef ), 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 ) switch ( type )
{ {
// anonymous class // anonymous class
case 'object': case 'object':
result = createAnonymousClass.apply( null, arguments ); result = createAnonymousClass.apply( null, args );
break; break;
// named class // named class
case 'string': case 'string':
result = createNamedClass.apply( null, arguments ); result = createNamedClass.apply( null, args );
break; break;
default: default:
@ -99,10 +104,7 @@ module.exports = function( namedef, def )
* *
* @return {Function} extended class * @return {Function} extended class
*/ */
module.exports.extend = function( baseordfn, dfn ) module.exports.extend = extend;
{
return extend.apply( this, arguments );
};
/** /**
@ -138,11 +140,11 @@ module.exports.implement = function( interfaces )
*/ */
module.exports.use = function( traits ) module.exports.use = function( traits )
{ {
var args = [], i = arguments.length;
while( i-- ) args[ i ] = arguments[ i ];
// consume traits onto an empty base // consume traits onto an empty base
return createUse( return createUse( _nullf, args );
_nullf,
Array.prototype.slice.call( arguments )
);
}; };
@ -292,7 +294,10 @@ function createStaging( cname )
return { return {
extend: function() 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 // extend() takes a maximum of two arguments. If only one
// argument is provided, then it is to be the class definition. // argument is provided, then it is to be the class definition.
@ -308,21 +313,24 @@ function createStaging( cname )
implement: function() 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 // implement on empty base, providing the class name to be used once
// extended // extended
return createImplement( return createImplement( null, args, cname );
null,
Array.prototype.slice.call( arguments ),
cname
);
}, },
use: function() use: function()
{ {
return createUse( var args = [],
_nullf, i = arguments.length;
Array.prototype.slice.call( arguments )
); while ( i-- ) args[ i ] = arguments[ i ];
return createUse( _nullf, args );
}, },
}; };
} }
@ -349,14 +357,14 @@ function createImplement( base, ifaces, cname )
var partial = { var partial = {
extend: function() extend: function()
{ {
var args = Array.prototype.slice.call( arguments ), var an = arguments.length,
def = args.pop(), def = arguments[ an - 1 ],
ext_base = args.pop() ext_base = ( an > 1 ) ? arguments[ an - 2 ] : null
; ;
// if any arguments remain, then they likely misunderstood what this // if any arguments remain, then they likely misunderstood what this
// method does // method does
if ( args.length > 0 ) if ( an > 2 )
{ {
throw Error( throw Error(
"Expecting no more than two arguments for extend()" "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) // much more performant (it creates a subtype before mixing in)
use: function() 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( return createUse(
function() { return partial.__createBase(); }, function() { return partial.__createBase(); },
traits traits
@ -468,9 +481,9 @@ function createUse( basef, traits, nonbase )
// given during the extend operation // given during the extend operation
partial.extend = function() partial.extend = function()
{ {
var args = Array.prototype.slice.call( arguments ), var an = arguments.length,
dfn = args.pop(), dfn = arguments[ an - 1 ],
ext_base = args.pop(), ext_base = ( an > 1 ) ? arguments[ an - 2 ] : null,
base = basef(); base = basef();
// extend the mixed class, which ensures that all super references // 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 // call simply to mix in another trait
partial.use = function() partial.use = function()
{ {
var args = [],
i = arguments.length;
while ( i-- ) args[ i ] = arguments[ i ];
return createUse( return createUse(
function() function()
{ {
return partial.__createBase(); return partial.__createBase();
}, },
Array.prototype.slice.call( arguments ), args,
nonbase nonbase
); );
}; };
@ -557,8 +575,14 @@ function createMixedClass( base, traits )
*/ */
function extend( _, __ ) 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 // 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 // set up some additional convenience props
setupProps( new_class ); setupProps( new_class );
@ -584,10 +608,9 @@ function extend( _, __ )
*/ */
var implement = function( baseobj, interfaces ) var implement = function( baseobj, interfaces )
{ {
var args = Array.prototype.slice.call( arguments ), var an = arguments.length,
dest = {}, dest = {},
base = args.pop(), base = arguments[ an - 1 ],
len = args.length,
arg = null, arg = null,
implemented = [], implemented = [],
@ -595,9 +618,9 @@ var implement = function( baseobj, interfaces )
; ;
// add each of the 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) // copy all interface methods to the class (does not yet deep copy)
util.propParse( arg.prototype, { util.propParse( arg.prototype, {
@ -678,10 +701,10 @@ function attachImplement( func )
{ {
util.defineSecureProp( func, 'implement', function() util.defineSecureProp( func, 'implement', function()
{ {
return createImplement( var args = [], i = arguments.length;
func, while( i-- ) args[ i ] = arguments[ i ];
Array.prototype.slice.call( arguments )
); return createImplement( func, args );
}); });
} }
@ -699,11 +722,10 @@ function attachUse( func )
{ {
util.defineSecureProp( func, 'use', function() util.defineSecureProp( func, 'use', function()
{ {
return createUse( var args = [], i = arguments.length;
function() { return func; }, while( i-- ) args[ i ] = arguments[ i ];
Array.prototype.slice.call( arguments ),
true return createUse( function() { return func; }, args, true );
);
} ); } );
} }

View File

@ -33,7 +33,7 @@ var Class = require( __dirname + '/class' );
*/ */
module.exports = exports = function() module.exports = exports = function()
{ {
markAbstract( arguments ); markAbstract( arguments[ arguments.length - 1 ] );
// forward everything to Class // forward everything to Class
var result = Class.apply( this, arguments ); var result = Class.apply( this, arguments );
@ -56,7 +56,7 @@ module.exports = exports = function()
*/ */
exports.extend = function() exports.extend = function()
{ {
markAbstract( arguments ); markAbstract( arguments[ arguments.length - 1 ] );
return Class.extend.apply( this, arguments ); return Class.extend.apply( this, arguments );
}; };
@ -92,18 +92,12 @@ exports.implement = function()
/** /**
* Causes a definition to be flagged as abstract * Causes a definition to be flagged as abstract
* *
* This function assumes the last argument to be the definition, which is the * @param {*} dfn suspected definition object
* common case, and modifies the object referenced by that argument.
*
* @param {Arguments} args arguments to parse
* *
* @return {undefined} * @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' ) if ( typeof dfn === 'object' )
{ {
// mark as abstract // mark as abstract
@ -141,7 +135,7 @@ function abstractOverride( obj )
// wrap extend, applying the abstract flag // wrap extend, applying the abstract flag
obj.extend = function() obj.extend = function()
{ {
markAbstract( arguments ); markAbstract( arguments[ arguments.length - 1 ] );
return extend.apply( this, arguments ); return extend.apply( this, arguments );
}; };

View File

@ -29,7 +29,7 @@ var Class = require( __dirname + '/class' );
*/ */
exports = module.exports = function() exports = module.exports = function()
{ {
markFinal( arguments ); markFinal( arguments[ arguments.length - 1 ] );
// forward everything to Class // forward everything to Class
var result = Class.apply( this, arguments ); var result = Class.apply( this, arguments );
@ -50,7 +50,7 @@ exports = module.exports = function()
*/ */
exports.extend = function() exports.extend = function()
{ {
markFinal( arguments ); markFinal( arguments[ arguments.length - 1 ] );
return Class.extend.apply( this, arguments ); return Class.extend.apply( this, arguments );
}; };
@ -58,18 +58,12 @@ exports.extend = function()
/** /**
* Causes a definition to be flagged as final * Causes a definition to be flagged as final
* *
* This function assumes the last argument to be the definition, which is the * @param {!Arguments} dfn suspected definition object
* common case, and modifies the object referenced by that argument.
*
* @param {!Arguments} args arguments to parse
* *
* @return {undefined} * @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' ) if ( typeof dfn === 'object' )
{ {
// mark as abstract // mark as abstract
@ -92,7 +86,7 @@ function finalOverride( obj )
// wrap extend, applying the abstract flag // wrap extend, applying the abstract flag
obj.extend = function() obj.extend = function()
{ {
markFinal( arguments ); markFinal( arguments[ arguments.length - 1 ] );
return extend.apply( this, arguments ); return extend.apply( this, arguments );
}; };
} }

View File

@ -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 ) var extend = ( function( extending )
{ {
return function extend() return function extend()
@ -184,9 +201,10 @@ var extend = ( function( extending )
// ensure we'll be permitted to instantiate interfaces for the base // ensure we'll be permitted to instantiate interfaces for the base
extending = true; extending = true;
var args = Array.prototype.slice.call( arguments ), var a = arguments,
props = args.pop() || {}, an = a.length,
base = args.pop() || Interface, props = ( ( an > 0 ) ? a[ an - 1 ] : 0 ) || {},
base = ( ( an > 1 ) ? a[ an - 2 ] : 0 ) || Interface,
prototype = new base(), prototype = new base(),
iname = '', iname = '',
@ -210,50 +228,43 @@ var extend = ( function( extending )
var new_interface = createInterface( iname ); 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 _ithrow( iname, TypeError(
throw TypeError( 'Unexpected internal error' ); "Member " + name + " must be public"
}, ) );
}
getset: function() member_builder.buildMethod(
{ members, null, name, value, keywords,
// should never get to this point because of assumeAbstract null, 0, {}, vstate
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;
}
attachExtend( new_interface ); attachExtend( new_interface );
attachStringMethod( new_interface, iname ); attachStringMethod( new_interface, iname );

View File

@ -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 * Parses object properties to determine how they should be interpreted in an
* Object Oriented manner * Object Oriented manner
@ -268,6 +281,8 @@ exports.propParse = function( data, options, context )
callbackGetSet = options.getset || fvoid, callbackGetSet = options.getset || fvoid,
keywordParser = options.keywordParser || propParseKeywords, keywordParser = options.keywordParser || propParseKeywords,
throwf = options._throw || _throw,
hasOwn = Object.prototype.hasOwnProperty, hasOwn = Object.prototype.hasOwnProperty,
parse_data = {}, parse_data = {},
@ -315,12 +330,12 @@ exports.propParse = function( data, options, context )
if ( !( value instanceof Array ) ) if ( !( value instanceof Array ) )
{ {
throw TypeError( throwf( TypeError(
"Missing parameter list for abstract method: " + name "Missing parameter list for abstract method: " + name
); ) );
} }
verifyAbstractNames( name, value ); verifyAbstractNames( throwf, name, value );
value = exports.createAbstractMethod.apply( this, 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 * In the future, we may add additional functionality, so it's important to
* restrict this as much as possible for the time being. * 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 {string} name name of abstract member (for error)
* @param {Object} params parameter list to check * @param {Object} params parameter list to check
* *
* @return {undefined} * @return {undefined}
*/ */
function verifyAbstractNames( name, params ) function verifyAbstractNames( throwf, name, params )
{ {
var i = params.length; var i = params.length;
while ( i-- ) while ( i-- )
{ {
if ( params[ i ].match( /^[a-z_][a-z0-9_]*$/i ) === null ) if ( params[ i ].match( /^[a-z_][a-z0-9_]*$/i ) === null )
{ {
throw SyntaxError( throwf( SyntaxError(
"Member " + name + " contains invalid parameter '" + "Member " + name + " contains invalid parameter '" +
params[ i ] + "'" params[ i ] + "'"
); ) );
} }
} }
} }
@ -399,7 +416,10 @@ function verifyAbstractNames( name, params )
*/ */
exports.createAbstractMethod = function( def ) 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() var method = function()
{ {
@ -407,7 +427,7 @@ exports.createAbstractMethod = function( def )
}; };
exports.defineSecureProp( method, 'abstractFlag', true ); exports.defineSecureProp( method, 'abstractFlag', true );
exports.defineSecureProp( method, 'definition', definition ); exports.defineSecureProp( method, 'definition', dfn );
exports.defineSecureProp( method, '__length', arguments.length ); exports.defineSecureProp( method, '__length', arguments.length );
return method; return method;
@ -562,8 +582,8 @@ exports.defineSecureProp( exports.getPropertyDescriptor, 'canTraverse',
/** /**
* Appropriately returns defineSecureProp implementation to avoid check on each * Appropriately returns defineSecureProp implementation to avoid check on
* invocation * each invocation
* *
* @return {function( Object, string, * )} * @return {function( Object, string, * )}
*/ */
@ -584,26 +604,14 @@ function getDefineSecureProp()
// uses ECMAScript 5's Object.defineProperty() method // uses ECMAScript 5's Object.defineProperty() method
return function( obj, prop, value ) return function( obj, prop, value )
{ {
try Object.defineProperty( obj, prop,
{ {
Object.defineProperty( obj, prop, value: value,
{
value: value,
enumerable: false, enumerable: false,
writable: false, writable: false,
configurable: 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 );
}
}; };
} }
} }

View File

@ -89,8 +89,6 @@ exports.report = function( count, desc )
pers = ( total / count ).toFixed( 10 ) pers = ( total / count ).toFixed( 10 )
; ;
console.log( total + "s (x" + count + " = " + pers + "s each)" + console.log( "%s|%s|%s|%s", desc, count, pers, total );
( ( desc ) ? ( ': ' + desc ) : '' )
);
}; };

View File

@ -105,3 +105,32 @@ common.test( function()
// run the same test internally // run the same test internally
foo.testInternal(); 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' );
} )();

View File

@ -0,0 +1,30 @@
#!/bin/bash
#
# 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 <http://www.gnu.org/licenses/>.
#
# If you want formatted output, see :/tools/perf2html, or run this command:
# $ column -ts\| perf.out
# #
for f in "$@"; do
@NODE@ "$f" || exit $?
done \
| tee >( awk -F\| '
# format for display as the tests are running
{ printf "%s (x%s = %ss each): %s\n", $4, $2, $3, $1 }
' >&2 )

97
tools/perf2html 100755
View File

@ -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 <http://www.gnu.org/licenses/>.
#
# 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 "<span class=\"" class "\">" value "</span>"
}
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 "<!DOCTYPE html>" \
"<html>" \
"<head>" \
"<title>GNU ease.js Performance Test Results</title>" \
"<style type=\"text/css\">" \
"table th:first-child { text-align: left; }" \
"table td { padding: 0.1em 0.5em; }" \
"table td:not(:first-child) { text-align: right; }" \
".slow { color: #c4a000; font-style: italic; }" \
".very.slow { color: red; }" \
"</style>" \
"</head>" \
"<body>" \
"<h1>GNU ease.js Performance Test Results</h1>" \
"<table>" \
"<thead>" \
"<tr>" \
"<th>Description</th>" \
"<th>Iterations</th>" \
"<th>Seconds/iter</th>" \
"<th>Total (s)</th>" \
"</tr>" \
"</thead>" \
"<tbody>"
}
# format row of output (desc, count, time, total)
{
runtime += $COL_TOTAL
printf "<tr>"
for ( i = 1; i <= NF; i++ )
{
printf "<td>%s</td>", styleout( i )
}
printf "</tr>\n"
}
END {
# footer
print "</tbody>" \
"</table>" \
"<p>Total running time: " runtime " seconds</p>" \
"</body>" \
"</html>"
}