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

Sunday, March 28, 2010

new Constructor VS Object.create

just a quick post about ES5 Object.create performances. While in More ES5 Friendly Patterns paragraph I have described how to use new ES5 features to create instances in a better way, I have never tested directly performances against classic ES3 pattern.

The Benchmark Logic

Pretty simple, create a new object with a "privileged property" plus an inherited one. The test prototype looks like this object:

var proto = {toString:function () {
return this.name;
}}

The assumption is that somehow the object should be able to return it's name, which is not shared via prototype.

ES3 Game

I hope I don't have to explain this piece of code:

function F(name) {
this.name = name;
}
F.prototype = proto;

To test above classical pattern, all we need is to confirm this behavior:

var o = new F("instance");
alert(String(o) === "instance");


ES5 Game

Using an updated browser, it should be possible to replicate ES3 behavior via this piece of code:

var o = Object.create(proto, {
name: {
value:"object"
}
});

Right, Object.create comes with much more power than a simple property assignment, but here we are testing a basic task just to analyze what's the outcome, right? Just to be sure about the result:

alert(String(o) === "object");


Optimized ES5 Game

What can we do to improve performances? Avoid global scope look up, the Object constructor, plus use a static/fixed property to cheat the test:

var fixed = {
name: {
value: "static"
}
};

var create = Object.create;

var o = create(proto, fixed);

// the assertion
alert(String(o) === "static");


The Benchmark

Let's put these patterns together inside a closure, execute them 100000 times, retrieving elapsed time.

setTimeout(function () {

var proto = {toString:function () {
return this.name;
}};

var fixed = {
name: {
value: "static"
}
};

function F(name) {
this.name = name;
}
F.prototype = proto;

var o;

for(var i = 100000, instance = new Date; i--;) {
o = new F("instance");
}
instance -= new Date;

for(var i = 100000, object = new Date; i--;) {
o = Object.create(proto, {
name: {
value:"object"
}
});
}
object -= new Date;

for(var create = Object.create,
i = 100000, static = new Date; i--;
) {
o = create(proto, fixed);
}
static -= new Date;

alert([instance, object, static].join("\n").replace(/-/g,"ms: "));

}, 1000);


The Result

Based on Atom N270 nad testing via Chrome 5 for devs, this is what I read in the alert:

ms: 7
ms: 2325
ms: 2218

The first result goes up to 54ms, reaching 5ms, the second one is pretty much stable while the latest one goes down 'till 1987 ms ...

Conclusion

new Constructor seems to be the best option for those cases where the initialize/init/construct method needs to somehow setup the created instance. Only if we need special Object.create features it is worth it to use it, but if we base a whole framework over Object.create, considering eventually emulations for old browsers, this framework will be inevitably slower than older code. This means that as example a classic Point class option, should still be exactly like this:

function Point(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}

or eventually like the next one, if we don't need chained methods, neither new before each call:

function Point(x, y, z) {
return {x:x, y:y, z:z};
}

by my good old AStar algorithm time, the fastest option ever!

6 comments:

Lars Gunther (itpastorn) said...

Perhaps its a bit premature to test the performance for ES5. But filing a bug for Chrome about the deficience might be a good idea.

Dmitry A. Soshnikov said...

Thanks for this analysis, but it also depends on implementation; e.g. in Rhino I have the following results:

ms: 1078
ms: 1875
ms: 1094

And in other call already the following (the first approach is less effective):

ms: 1109
ms: 1891
ms: 1094

The second approach should be less effective also because it should resolve "Object.create" accessor: first "Object" name, then find ".create" name in it. After that -- to resolve "proto" name also. Of course, creating a new properties-object on each iteration also makes performance issue.

The third case also should resolve three names ("create", "proto" and "fixed"), but there is improvement because of one instance of properties-object.

Theoretically, but the ECMA-262-5, [[Construct]] which is called by new should work faster if simply to compare algorithms.

Object.create (15.2.3.5) should first do all work of new Object (15.2.2.1) -- with all checks, etc. After that again set [[Prototype]] property (which means it do it twice in contrast with new) and then call Object.defineProperties (15.2.3.7) which also makes own checks, conversions, iteration over properties and so on -- again -- in contrast with a function call to initialize newly created object, though, a function call is also not so cheep operation.

P.S.: static is future reserved word, should be a SyntaxError.

Dmitry.

Andrea Giammarchi said...

@itpastorn I do believe once you implement something, something should be implemented properly, but maybe there is no JIT optimization for such operation, still weird but a bug must be probably fired.

@Dmitry there is still a consistent gap between the ES3 way and the ES5 one. I will test Rhino and others as well via my Atom, but the point is: think about Object.create shims, if the native is still slower, how much a shim will impact performances?
In few words, where performances matters, and we don't need Object.create features, it's not worth it at all to prefer latter pattern rather than good old new Constructor.
My attempt was the one to underline all differences you described indeed, I would rather define properties directly to the prototype avoiding create patern where necessary.
As we all know, more we move forward, less power computation we have to deal with (mobile, netbook, etc) so these considerations must be done.

I know about static, nice one nothing happened in my tests :D

NetBen said...

Andrea, the benchmarks are great and interresting, though I do feel some emphasis must be made that programmers should avoid creating lots of objects in the first place and reuse already created object through delegation.

With that in mind, performance is less of an issue and readability and maintenance can be prioritized. Also, the best way to create 'simple' objects is through literal object notation:

var myPoint = {x:1,y:1,z:1};

It does not need to be a class or prototype really ;o)

Like I said, the benchmarks are really interresting, but we have a long history in optimizing JS because we had to. However we are already at the point writing smarter architectures (design patterns) offers a higher gain in performance then micro optimisation.

Very curious to hear your opinion on this ;O) as I know you are a micro optimiser galore =P

Andrea Giammarchi said...

the literal notation is surely the fastest option but as you said better patterns mke life easier and a centralized "place" to create a Point is a better pattern, imho, so that we can add more to Points, if necessary, when necessary, and in one place.

About micro optimizations, as I have said, more we move forward with complex applications, more we have to deal with portable/mobile devices where power computation is crap.

If you have to deal with ARM processors on daily basis, you'll notice a massive boost using best practices, same is for Netbook and these CPUs (Atom and others) and this is something developers keep forgetting, testing with their supadupa hardware accelerated PCs/Laptops creating *monsters* for users with devices theoretically made to surf the net and nothing else.

The key is to find a good compromise, after being aware about these issues/problems/techniques

Angus Croll said...

Thanks Andrea - this is interesting

A lot of the headache with inheritance in JS is that ECMA never created a standard for it - so developers were left in their own and now we have code wars...and everyone BASHES new which is a shame because it is transparent and performs the best and you need to know Javascript to use it properly.

So finally ECMA have a standard but clearly the probelm is not solved