diff --git a/.gitignore b/.gitignore
index f0d5048..7404829 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.*
+
# should be added using autoreconf -i
INSTALL
tools/install-sh
diff --git a/Makefile.am b/Makefile.am
index 752c710..3a4f291 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -111,11 +111,14 @@ html-single:
test: check
check: $(src_tests) test-suite
-# performance tests
-perf: @PERF_TESTS@
-perf-%.js: FORCE
+# 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
- @$(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/lib/ClassBuilder.js b/lib/ClassBuilder.js
index bd2e270..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
@@ -299,9 +297,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 +507,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..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()
@@ -184,9 +201,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 = '',
@@ -210,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 8c3cf00..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 ] + "'"
- );
+ ) );
}
}
}
@@ -399,7 +416,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 +427,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;
@@ -562,8 +582,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 +604,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,
+ } );
};
}
}
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/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' );
+} )();
+
diff --git a/test/perf/runner.in b/test/perf/runner.in
new file mode 100755
index 0000000..ce046e2
--- /dev/null
+++ b/test/perf/runner.in
@@ -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
Description | " \ + "Iterations | " \ + "Seconds/iter | " \ + "Total (s) | " \ + "
---|---|---|---|
%s | ", styleout( i ) + } + printf "
Total running time: " runtime " seconds
" \ + "" \ + "" +}