My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Thursday, February 19, 2009

On JavaScript Inheritance Performance and Libraries Troubles

Update: I have replied to myself and developers in a new post. I would like to say a big Thank You to every developer exposed tests, benchmark, traps, and considerations. Please read my last thoughts about the subject, since I deeply reconsidered my position.
Update: Please do not get me wrong. I have no intention to say that one or any of cited framework, piece of code, library, is not good or fast enough to extend other classes. This post is about maniac optimization based on personal considerations over some deeper analysis to complete in a way the first benchmark.
I am not criticizing libraries, they are great and they offer everything, I am simply showing a specific case, which is particular on purpose, and a specific behavior that, useful or not, could not be respected.


About


Few days ago Ajaxian published a post about JS inherited methods performances via common libraries strategies.
I think the argument is extremely interesting but there's not enough material yet, so here I am with my contribution plus a "best option" proposal.

Why We Need Libraries to Extend Functions


  • libraries (should) manage properly the prototypal chain

  • libraries (should) make code more elegant/readable

  • libraries (should) let us create overrides being able to call parent/super method implicitly if necessary

  • libraries are often based over a generic extended "class" or hierarchy and users should be familiar with that way to extend or create classes in order to avoid internal conflicts



Why We Do Not Need Libraries to Extend Functions


  • we are performances or bytes maniacs and we are scared by library obtrusive implementations

  • we don't trust the library pattern/strategy to extend function - we know a better way to do it simply and quickly

  • we would like to create hybrids unmanageable via used library


About above points, let me answer to this question:
"Which Common Code/Library is Good and Fast to Extend Properly?" Considering the YUI approach out of the game, NOT A SINGLE ONE!!!

Tested Libraries in Alphabetic Order

base2, dojo, jClass, MooTools, Prototype, WebReflection (blog proposal), and YUI


About the Test

I tried to create a particular inheritance case.
There is a parent class with a toString and a toLocaleString prototype method.
These methods are particular since they are hidden methods (read: native) and browsers like Internet Explorer do not discover hidden methods even if those have been explicitly assigned.
The extended class should be able to understand the inheritance without problems and produce the expected result, showed in the Ad Hoc example.


Ad Hoc concept and troubles


  • Concept: manual inheritance over usable constructors.

  • Troubles: requires a bit of skill and parent calls are always explicit.



// manual implementation
// best performances and expected behavior in every browser
function AdHocClass(name){
this.name = name;
};
AdHocClass.prototype.toString = function(){
return this.name;
};
AdHocClass.prototype.toLocaleString = function(){
return "locale: " + this.toString();
};
function AdHocSub(name, age){
AdHocClass.call(this, name);
this.age = age;
};
AdHocSub.prototype = new AdHocClass;
AdHocSub.prototype.constructor = AdHocSub;
AdHocSub.prototype.toString = function(){
return AdHocClass.prototype.toString.call(this) + ": " + this.age;
};

// This is the expected result We would like to obtain
var me = new AdHocSub("Andrea", 30);
alert(me); // Andrea: 30
alert(me.toLocaleString()); // locale: Andrea: 30



base2


  • Concept: runtime injected parent (called via base) over explicit initialization via init method

  • Troubles: every instance calls two functions when initialized: the contructor plus the init method. This slows down performances. At the same time, not every method is inherited properly.



var BaseClass = base2.Base.extend({
constructor:function(name){
this.name = name
},
toString:function(){
return this.name;
},
toLocaleString:function(){return "locale: " + this.toString()}
});
var BaseSub = BaseClass.extend({
constructor:function(name, age){
this.base(name);
this.age = age;
},
toString:function(){
return this.base() + ": " + this.age;
}
});
var me = new BaseSub("Andrea", 30);
alert(me); // Andrea: 30
alert(me.toLocaleString()); // ERROR: same as toString both IE and Others



dojo


  • Concept: lazy dependencies resolution via the initializer method

  • Troubles: the initializer method is not that linear and it is probably the slowest in the list. Sometimes we need to call explicitly the method, as first argument, while sometimes we need to modify the arguments object or create an array to call the parent method. I am not sure I used dojo best practices to replicate the scenario but that's what I found googling for a while (Update: thanks to Eugene Lazutkin I could use a better practice which improved consistently performances). Is the rest fine? Not really, Internet Explorer does not inherit some hidden method. Update: apparently dojo offers different possibilities to extend and "play" with parents, so this is just one of them.


dojo.declare("dojoClass", null, {
constructor:function(name){
this.name = name;
},
toString:function(){
return this.name;
},
toLocaleString:function(){return "locale: " + this.toString()}
});
dojo.declare("dojoSub", dojoClass, {
constructor:function(name, age){
this.age = age;
},
toString:function($super){
return this.inherited("toString", arguments) + ": " + this.age;
}
});
var me = new dojoSub("Andrea", 30);
alert(me); // Andrea: 30 - not in IE
alert(me.toLocaleString()); // ERROR: same as toString in IE



jClass


  • Concept: basically the same one used by base2

  • Troubles: even worse than base2 since there are no explicit searches for toString and valueOf (most common hidden methods)



var ResigClass = jClass.extend({
init:function(name){
this.name = name;
},
toString:function(){
return this.name;
},
toLocaleString:function(){return "locale: " + this.toString()}
});
var ResigSub = ResigClass.extend({
init:function(name, age){
this._super(name);
this.age = age;
},
toString:function(){
return this._super() + ": " + this.age;
}
});
var me = new ResigSub("Andrea", 30);
alert(me); // Error: [object Object] in IE
alert(me.toLocaleString()); // Error: [object Object] in IE



MooTools


  • Concept: runtime injected parent, similar to base2. In my opinion the most clean and elegant way to extend a class.

  • Troubles: Classes are created via Class constructor so we have same bottleneck spotted in base2. Inheritance problems are basically the same found in base2: some hidden method is not inherited.



var MooToolsClass = new Class({
initialize: function(name){
this.name = name;
},
toString:function(){
return this.name;
},
toLocaleString:function(){return "locale: " + this.toString()}
});
var MooToolsSub = new Class({
Extends:MooToolsClass,
initialize:function(name, age){
this.parent(name);
this.age = age;
},
toString:function($super){
return this.parent() + ": " + this.age;
}
});
var me = new MooToolsSub("Andrea", 30);
alert(me); // Andrea: 30 - not inherited in IE
alert(me.toLocaleString()); // ERROR: locale: Andrea in IE



Prototype


  • Concept: $super variable as parent sent as first argument, imho the most obtrusive way so far and not elegant/intuitive at all.

  • Troubles: something works as expected, but only toString and valueOf, as is for base2, are considered hidden methods. The extra argument and the method overload make this library almost slow as dojo inheritance implementation is.



var ProtoClass = Class.create({
initialize:function(name){
this.name = name;
},
toString:function(){
return this.name;
},
toLocaleString:function(){return "locale: " + this.toString()}
});
var ProtoSub = Class.create(ProtoClass, {
initialize:function($super, name, age){
$super(name);
this.age = age;
},
toString:function($super){
return $super() + ": " + this.age;
}
});
var me = new ProtoSub("Andrea", 30);
alert(me); // Andrea: 30
alert(me.toLocaleString()); // ERROR: same as toString in IE




YUI


  • Concept: a constructor link to its parent prototype via "this.constructor.superclass". No runtime operations, no injected overloads, just "raw methods" based on explicit calls.

  • Troubles: not really. The YUI approach is extremely simple and efficient then "of course it works"!. There are just a couple of steps resolved via YAHOO.lang.extend: the correct constructor reassignment after the prototype one, plus the superclass link. I have just a comment about YUI implementation: guys, why do not you avoid that function creation for each extend call?



// No var F = function(){}, i SUGGESTION
extend: function(F){
return function(subc, superc, overrides) {
if (!superc||!subc) {
throw new Error("extend failed, please check that " +
"all dependencies are included.");
}
F.prototype=superc.prototype;
subc.prototype=new F;
subc.prototype.constructor=subc;
subc.superclass=superc.prototype;
if (superc.prototype.constructor == OP.constructor) {
superc.prototype.constructor=superc;
}

if (overrides) {
for (var i in overrides) {
if (L.hasOwnProperty(overrides, i)) {
subc.prototype[i]=overrides[i];
}
}

L._IEEnumFix(subc.prototype, overrides);
}

}
}(function(){}),

And here we are with the scenario:

function YUIClass(name){
this.name = name;
};
YUIClass.prototype.toString = function(){
return this.name;
};
YUIClass.prototype.toLocaleString = function(){
return "locale: " + this.toString();
};
function YUISub(name, age){
this.constructor.superclass.constructor.call(this, name);
this.age = age;
};
YAHOO.lang.extend(YUISub, YUIClass);
YUISub.prototype.toString = function(){
return this.constructor.superclass.toString.call(this) + ": " + this.age;
};
var me = new YUISub("Andrea", 30);
alert(me); // Andrea: 30
alert(me.toLocaleString()); // locale: Andrea: 30



WebReflection Proposal

After an analysis like this one, how could I skip my "all the best from others without troubles" proposal?
These are my considerations:

  • The fastest way to use a parent method is an explicit call

  • The best way to perform above step is via a link able to remove explicit dependencies (read: MyParentClassName instead of this.constructor.superclass). In this way methods could be easily transported from a prototype to another one without problems (less memory usage and less code to maintain)

  • The this.parent() solution is the cleanest one and generally speaking more close to our concept of "Object Oriented JavaScript". It is portable, it is meaningful, it is shorter than an explicit call with or without a link but it is not that fast to execute.

  • Using best practices to cache all we need, create a quick wrapper, resolving hidden methods problems, will not bring us to native or YUI performances, but at least in a good scenario: fast enough to guarantee performances over code elegance and readability


So here we are with my proposal:

  • Concept: parent in prototype but changed runtime for hierarchy purpose only if necessary (read: only for overrides). This will allows us to call parent directly in the constructor, as example, so we won't have double calls for each instance. At the same time, this implementation lets us call explicitly a parent method from those whose were not inherited.

  • Troubles: performances are the best but still far from native one.



function WRClass(name){
this.name = name;
};
WRClass.prototype.toString = function(){
return this.name;
};
WRClass.prototype.toLocaleString = function(){
return "locale: " + this.toString();
};
function WRSub(name, age){
this.parent(name);
this.age = age;
};
wr.extend(WRSub, WRClass, {
toString:function(){
return this.parent() + ": " + this.age;
}
});
var me = new WRSub("Andrea", 30);
alert(me); // Andrea: 30
alert(me.toLocaleString()); // locale: Andrea: 30

// last, but not least
WRSub.prototype.explicitParentToString = function(){
return this.parent.prototype.toString.call(this);
// instead of
return this.constructor.superclass.toString.call(this);
};
alert(me.explicitParentToString()); // Andrea



WebReflection proposed Extend

For future improvements/bugs fixes, please use this link.

var wr = {
extend:function(parent, extend){
/**
* (C) Andrea Giamamrchi
* Mit Style License
*/
return function(self, Function, Object){
var prototype = function(){
this.prototype[key] =
this.prototype.parent && typeof Object[key] == "function" && typeof this.prototype[key] == "function" ?
extend(Function, this.prototype[key], Object[key]) :
Object[key]
;
};
if(Object){
parent.prototype = Function.prototype;
self.prototype = new parent;
self.prototype.constructor = self;
self.prototype.parent = Function.prototype.parent ?
extend(Function, Function.prototype.parent, Function) : Function
} else
Object = Function;
for(var key in Object)
prototype.call(self);
for(key in {toString:key})
return self;
//* ... for Internet Explorer only ...
for(var
split = "hasOwnProperty.isPrototypeOf.propertyIsEnumerable.toLocaleString.toString.valueOf".split(".");
key = split.shift();
)
if(Object.hasOwnProperty(key))
prototype.call(self);
//*/
return self
}
}(
function(){},
function(parent, extend, Function){
return function(){
this.parent = extend;
var result = Function.apply(this, arguments);
this.parent = parent;
return result
}
}
)
};



The Benchmark

I had to choose Prototype or MooTools, since these frameworks seem to have some problem to coexist. Since Prototype has been tested in the Ajaxian post, I decided to put MooTools in the middle.
Something to consider before you try the benchmark page ... I removed the purposeless direct method call since it is absolutely the same for every basic class, created via library or not.
The order is by performances, where generally speaking are these:

  1. Ad Hoc Manual Explicit Inheritance

  2. YUI lang.extend (close to Ad Hoc)

  3. wr.extend - WebReflection proposal (closer to jClass than YUI)

  4. jClass (truly close to base2)

  5. base2 (truly close to jClass)

  6. dojo

  7. Prototype

  8. MooTools


Try out The Benchmark

And have a nice week end ;-)

31 comments:

Anonymous said...

Your criticism that base2 fails on "toLocaleString" is pretty feeble. It is still possible to override this method in base2 but it is not part of the default class construction.

base2's inheritance mechanism is widely used and tested. No one has complained about "toLocaleString" so far.

base2 is built for fast and flexible object construction and method invocation. Have you looked at base2 properly?

Broofa said...

Andrea, Robert Kieffer here - the original author of inheritance performance on Ajaxian.

'Just wanted to say I *really* enjoyed this post. Thanks so much for doing this - you rock! I'm thrilled to see such an exhaustive and creative followup to my little thought exercise of a few days ago.

P.S. As the author of JSLitmus, I hope you found it easy to work with. Please let me know if you have any suggestions for improvement.

P.P.S. I think you're link to the Ajaxian article is broken (it has a trailing "<br>" in it.)

Andrea Giammarchi said...

Dean I have updated the post because I do not want you guys think this post is "a shame".
The toLocaleString is explicitly an excessive case to consider/analyze and this is on purpose.
My point is simply to demonstrate how things are with manual/YUI inheritance and how we, as developers, expect to obtain the same result, with every browser, whatever library we use.

I know probably nobody on earth is using one of those native methods I forceto check with my proposal in IE as I perfectly know that every framework could be readapted to sovle this issue/non-issue.

Apologies if my non-perfect English enthusiasm offended you or your wonderful job (I cannot image how other guys could feel since your method is, in a way, the most common/used one)

Andrea Giammarchi said...

@Robert,
thanks for the link, I fixed it.
I found JSLitmus both simple and useful so thank you for the tool.
I had some problem adding too many reports (the other reason U had to remove direct method call on base classes) while for the rest, as you said, it is about GB, other processes, etc. Generally speaking I have almost same results multiple times so it is reliable enough ;-)

Anonymous said...

Any chance that you add qooxdoo to the list? Would be interested in your analysis. Thanks.

See also:
http://qooxdoo.org/documentation/0.8/oo_feature_summary

Andrea Giammarchi said...

when those guys will let us test easily without a build I will :P

Ariel Flesler said...

John Resig's code doesn't seem to be called "jsClass". The one I did though (not in here), is indeed called like that.
Where did you get that name from ?

Andrea Giammarchi said...

I am sorry Ariel, you are absolutely right. Bad copy and paste, sorry again (so you have another library, uh? I would like to test it as well)

Sullof said...

Hi Andrea, great post.
Passpack - my startup - use extensively jQuery, but when I had to chose a "class implementation" for the application I had a look around and after some analisys I decided to use MooTools Class definition. I decided to mantain it also when John published his classy definition.
Now... I am afraid I have to test your proposal [smile]

Anonymous said...

hii, great comparison

Eugene Lazutkin said...

Your Dojo sample is convoluted --- why did you do the "initializer" function instead of initializing everything in the constructor like you did for other libraries? To add more weight?

Andrea Giammarchi said...

Eugene, I wrote I was not sure about dojo, specially because calling the initializer in the constructor caused exceptions and nothing else.
If you have a better example I'll use it without problems, but if you read the initializer code, I bet is not my double call the problem.

Eugene Lazutkin said...

I have corrected the Dojo snippet. You can find it here: http://pastebin.com/m139a72a7

The resulting chart is here: http://tinyurl.com/dzdxmq --- last three lines show some improvement over what you coded.

Eugene Lazutkin said...

In the real life when you have more "meat on the bones" --- constructors and methods are not trivial --- the packaging overhead is minimal and the best way to judge it by the flexibility and convenience.

Your tests have practically no code, so you measure almost the pure overhead. Obviously extra calls you did in the Dojo sample matter a lot in this scenario.

Andrea Giammarchi said...

I simply think dojo way is over-engineered and best practices or not I guess will be the slowest for runtime "discover way".

Thank you for the snippet, I'll update asap (it's Saturday night here :D)

Eugene Lazutkin said...

Thank you for your confidence in Dojo and your test.

If you looked in the chart I linked in my previous post you will see that this trivial change improved the performance of the class instantiation to 66.4K ops/s.

To put it in perspective: it is faster than your original snippet (55.1K), faster than MooTools (34.2K), faster than "Resig" (51.4K), and faster than Base (64.8K).

The same goes for the subclass instantiation: with 34.7K it is faster than Base (19K), MooTools (24.3K), and the your original Dojo snippet (14.8K).

What does it say to us? When small trivial changes (and re-runs of the test) produce drastically different results --- the test is not stable and cannot be used as a benchmark, unless your goal is to write toy programs without any useful actions. ;-)

Having said that I agree that Dojo trades convenience for speed --- that was the intentional decision. For fast OO there are low-level facilities: dojo.mixin(), dojo.extend(), dojo.delegate() to name a few. Nevertheless dojo.declare() will be updated soon, probably in Dojo 1.4. Speeding it up is one of goals.

Andrea Giammarchi said...

Eugene Lazutkin, thank you for the suggestion. I have updated the post, the image, the generic performances list, but I am sorry, even with your suggestion in my PC my proposal is still the fastest :P

Eugene Lazutkin said...

Andrea, I totally forgot to thank you for the article. Of course we (the JavaScript developers) want to know where different libraries stand performance-wise, what corner cases are "forgotten" and corners are cut, what trade-offs are made, and how we can improve our libraries and our code in general.

In spite of some minor irregularities (you promptly corrected them) it was really revealing to see popular libraries together with a way to improve them. I thoroughly enjoyed reading the article.

I hope we'll see more blog posts from you focused on performance, and functionality.

Andrea Giammarchi said...

@kangax, guys, I have modified the source since I will probably add more and more stuff and I agree with you that a proposal should be as clear as possible.
wr object

Andrea Giammarchi said...

Cheers Covex, I'll have a look later.
Regards

reiss said...

Hi there,

Great article. It inspired me to go a bit further and create this:

http://www.projectcss.net/inheritance/

I've not had a chance to add wr but I'll have it done soon. I only have this for wr so far:

http://www.projectcss.net/oop-test/2/

Thanks again for the informative post :o)

Andrea Giammarchi said...

reiss, please do not forget to test my proposal with new engines ala Chrome, Tracemonkey, and SquirrelFish Extreme since my code runs faster than others specially with these new engines :)
Interesting post btw, but an Iframe with wr proposal would be great :D

Andrea Giammarchi said...

@Covex ... really nice "trap" there.
To easily fix it I had to put 2 more lines in the extend function, the one to save runtime in-call changed parent plus a try catch to avoid interruption in the execution.
return function(){
var result, parent = this.parent;
this.parent = extend;
try{result = callback.apply(this, arguments)}catch(e){}
this.parent = parent;
return result;
}

at this point this method become reliable (it is still not perfect since I should thorw the Error if there is a problem) but probably one of the slowest.
I'll write something about soon since at this point I guess the YUI way is the best one.

reiss said...

Hi Andrea,

I tried adding wr to the tests, however, it is returning recursion errors. Please find the test on the following link:

http://www.projectcss.net/inheritance/testCode/wrClassCorrectness.html

If you can let me know what you think that would be good.

Thanks,

Reiss

Andrea Giammarchi said...

Guys, Reiss, please stop to use that version since I am working on a new one and I need to write down another post about this stuff.
Thanks for your tests in any case

Andrea Giammarchi said...

@Reiss ... wait a minute, I think there is no way to pass that test respecting the prototype chain (Boxer instanceof Fighter)
Because obviously if you have a superMethod that does alert("super");
and you inherit that method you are calling the same function twice in the inheritance hierarchy. You need to override the method in Boxer, as example, calling the parent(). If you do not define that method, the same will be called twice with every library unless it does not override internally every function (bye bye performances)

Andrea Giammarchi said...

Covex, your speed up proposal is hard coded but if it is that efficient maybe it is worthy :)

The only doubt I have is with Internet Explorer where the switch is not fast as is in every other browser (... I mean, the switch is another thing truly slow ...)

At the same time YUI way is the simplest/best because it is under control.

All these automation over callbacks have a lot of side effects, something I could pass over if everything work as expected, something that does not make sense since cases (traps) are too many to consider.

The Reiss logical inheritance test make sense in every language but requires a lot of overload in JavaScript.
I think for that test there is not a single library able to pass it except probably dojo, something I have to check.

What I am realizing (and the reson I gonna change my proposal soon) is that classical inheritance in JavaScript cannot coexist with performances and that parent/$super/whatever is just a convention that could simply produce side effects hard to debug.

Even the YUI library, with its superclass, suffers problems in multiple extended prototype so at the end of the day if we need to call the Parent.prototype.doStuff, the best/fastest way is to call it without all these overloads being sure everything is working as expected, whatever "noise" in the middle.

I am pro performances in this case, and pro debug prone code, so all we need is a good and reliable function to extend a constructor and nothing else.

reiss said...

@Andrea - Hi, you can do that test, you're calling the same method but in a different context. There are 8 class implementations now on my test page and they all pass this test so it must be possible lol

@Covex - Hi, I've added JooS to the tests, but it seems to crash my IE7 when running the performance test. It works fine in firefox though :o)

Having spent a while now testing the various implementations it's clear that performance hinges totally around how parent method calling is implemented. As I said over in the Ajaxian comments it's not something I use that often, and that typing an explicit call isn't hard lol

With that in mind I had a rethink and took j3Class back to basics. It now couldn't be any simpler, still supports parent method calling, and seems to out perform everything else.

The other implementations like jClass really have reached their performance limit, as they require 2 function calls for each call to a parent method, it just won't scale I don't think.

I anybody is interested it can be found at:

http://www.projectcss.net/inheritance/

If anybody has any ideas on how to do this type of thing any quicker it would be great to know.

Thanks,

Reiss :o)

Andrea Giammarchi said...

@Reiss, John way do not even pass the basic inheritance test (in your page) so I am sure it is possible to do that test but the other one with every PASSED result, I think not every library can pass it ... am I wrong?

Michael Heliso said...

I observed one thing about Base2 and Prototype. When you call a chain method and you reach the base method, inside the method you have access to the derived object members using “this”. In my opinion once the call is navigating thru the chain on each base class the user should not be able to access the derived class members based on OOP definitions. I also tried a different approach of JavaScript inheritance, it can make use of closures or prototype, it’s up to the way you want to implement it http://dotnetcaffe.blogspot.com/2009/12/javascript-inheritance.html

I agree with the fact that as more as yu try to get close to classical OO inheritance you trade of speed. But in some cases if you calculate your resource well it worth's it.

Regarding the explicit constructors, even if it’s more light weight as memory consumption I don’t find it elegant. In my case if the base class constructor requires parameters they will be passed from the derived class.
Also a check for parameters of the base constructor should be added. If the derived object does not provide on instantiation the correct number of parameters for the base, then an exception should be triggered.

Andrea Giammarchi said...

as more as you try to get close to classical OO inheritance you trade of speed
as more as you try to get close to classical OO inheritance, you should consider to use another programming language that is not JavaScript.
These are my latest thoughts about all this fuss, thanks for the link though ;)