Jump to content

How much memory do Function references consume?


rich
 Share

Recommended Posts

Just curious, but in the Google IO performance videos they talk about keeping the total number of object properties to around 35 or below. The key being that small well defined objects benefit from the most performance in V8, so long as they don't change shape and invalidate themselves.

 

But I was wondering if for example you've a Sprite object, and it has a render function - but that function is just a reference to a global sprite render function held elsewhere, does it copy the render function wholesale up into the size of the object, or is it literally just a tiny reference to the other function and thus consume far less overhead? I'm especially curious to know if it applies when the object is created too (even if created for a pool I still want to know if a Sprite with a reference to a global render method is bigger/smaller than a Sprite with its own render method).

 

Let me know if you need more details to understand what the hell I'm babbling about :)

Link to comment
Share on other sites

Here's a rough example to show what I mean:

 

Imagine there is a sprite render function. it's got all the code needed to render the sprite to the canvas/webgl/whatever. It could be may 100 lines once you factor in all of the things an advanced renderer can do.

 

Does it make any difference to the memory footprint / speed if this render function is part of the Sprite object itself, or if it's just stored once say in a SpriteRenderer object, and then sprite.render is just a reference to SpriteRenderer.render. I'm curious to know which has the most difference on memory / instantiation speed, or if actually there's no difference at all!

Link to comment
Share on other sites

There's a simple way of checking this:

var f = Array.indexOff == Array.indexOf // returns true

Both are referencing the same object, therefore there is only one function stored.

 

That's why it's better to use prototype function rather than adding functions in the constructor, which should create new functions for each instance of the class (should, because there are probably optimizations to prevent this memory loss).

 

EDIT: here is another example:

function Foo(){    this.f = function(){        // blah blah    }}console.log('Constructor: ' + (new Foo().f == new Foo().f)) // Returns falsefunction Bar(){}Bar.prototype.f = function(){    // blah blah}console.log('Prototype: ' + (new Bar().f == new Bar().f)) // Returns true
Link to comment
Share on other sites

Ok here's a better example, as I'm not talking about creating anonymous functions in constructors or anything like that:

 

Method 1:

function SpriteRenderer() {}SpriteRenderer.prototype.render = function(sprite) {// Lots of canvas context, transform, effects stuff going on here. Big function.}function Sprite() { this.render = SpriteRenderer.render;}

Method 2:

function Sprite() {}Sprite.render = function() {// The same amount of canvas context, transform, effects and such as in SpriteRenderer}

The difference being that you'd only ever create 1 SpriteRenderer object, but potentially thousands of Sprite objects.

Link to comment
Share on other sites

Yeah I'm suspecting (at least hoping!) they are actually the same internally. I guess the difference is that if you were to use Method 2 and then add a property to a Sprite for some reason (on purpose, by accident) then it will have changed its shape and I wonder if then suddenly it's no longer referring to some internal template but rather to its own copy of everything now.

Link to comment
Share on other sites

Thanks to the power of twitter (and the awesomeness of Paul Lewis) Google engineers confirmed:

 

@aerotwist: @photonstorm the most efficient pattern is:

function foo() { do_something(); }

function Obj() { this.fct = foo; }

var obj1 = new Obj();

 

@aerotwist: @photonstorm because it's the same constructor -> same hidden class -> most optimized code (taking this straight from my convo with eng.)

Link to comment
Share on other sites

Rich - that's not how Google Closure library is built https://developers.google.com/closure/library/, and not in line with Google JS code guidelines http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml (at least they don't advise against single prototype inheritance). 

 

It really seems to me that "prototype" chain should be heavily optimized in V8. So method 1 should really work great. I don't see how it interferes with object shapes at all. 

 

Because from pure memory perspective "render" is not even a link on Sprite object. It is a link on Sprite's prototype object (only one instance of this guy exists). Method 2 is not directly comparable, as it creates equivalent of static functions. 

 

EDIT: There's a discussion on how prototype chain is optimized is here: 

https://groups.google.com/forum/#!topic/v8-users/D_rvDRx00UY

My take away from it: it is probably a little bit more efficient (from access speed point) to create function directly in the constructor. Because having a "prototype" is a little bit like inheriting your instance from "main prototype" object, and there are lookup costs for that. But from memory perspective, having function on the prototype is more efficient. And they are also optimized as part of hidden class "detection". Also, given that all industry standard libs (including Google's own) are built on prototypes, it would be weird to not look into optimizing them heavily. 

Link to comment
Share on other sites

Rich - the problem with that code is that every object created from Obj() will contain its own reference to the foo() function. If you create a lot of sprites this way, it's unnecessary overhead. 

 

If you want to create a lot of sprites, the best way would be to use prototype. That way every object created will not have to have a separate reference to the same function.

Link to comment
Share on other sites

It really seems to me that "prototype" chain should be heavily optimized in V8. So method 1 should really work great. I don't see how it interferes with object shapes at all. 

 

 

Method 2 was the one that had the potential to cause trouble with object shape changes. Method 1 was the one I'm using right now, but have never actually benchmarked to prove there was any real benefit doing it that way.

Link to comment
Share on other sites

Method 2 was the one that had the potential to cause trouble with object shape changes. Method 1 was the one I'm using right now, but have never actually benchmarked to prove there was any real benefit doing it that way.

 

Method 2 does very different thing. After you do "var p = new Sprite", you cannot do p.render() in method 2. You can only do Sprite.render. So it is a rough analog of static methods. http://jsfiddle.net/MSvNR/

 

In method 2 you assign function as properties to constructor-function object Sprite. It does not affect instances that it produces in any way. And thus does not affect their shapes.

 

There are really no simple alternatives for method 1. "Prototype" is the only tool to add shared bags of properties to objects in JS (and there's no differentiation between properties and functions). This works in very simple way: if JS fails to find property (such as "render") on instance itself, it checks internal (and invisible/inaccessible in any standard way) prototype link of the object and tries to find this property on the object, referenced by this invisible/internal prototype link.

 

Non-standard __proto__ works in some browsers to check this (otherwise "secret") link.

 

It is also special JS behavior that every object produced with Sprite constructor, will get assigned to Sprite.prototype. By specially orchestrated coincidence Function instances in Javascript have predefined "prototype" property. This property allows us to easily specify prototypes for constructors (and that's the only way to provide prototypes for objects).

 

Note though - it has nothing to do with internal invisible/inaccessible prototype link of the function object itself (which will naturally be Function.prototype all the time). This point was particularly hard for me to grasp. 

 

Native functions are stored in exact same way. For example, it is easy to add your own function to every array in the program by doing Array.prototype.sortBackward = function() {}. After that [1, 2, 3].sortBackward will work normally. Or Function.prototype.bind.

 

No other properties share this semantics, so Sprite.render is ignored when "new Sprite" is invoked.

 

Only other alternative is for each object to carry full set of properties on its own (as suggested by aerotwist)

Link to comment
Share on other sites

Here, this is closer to what I was comparing: http://jsfiddle.net/Z5Vdn/

 

Quite simply just the difference between fully loading all the render code within Sprite (of which there will be many objects using the same shape) or SpriteRenderer (where there will only be 1 such object in the entire game).

 

ok - that's clear. Yeah, I would agree that this.render would take more memory (1 function pointer per instance), while slightly faster for access. And this.localRender is more memory-efficient (zero per-instance memory cost, no even links to this function exist on the instance level), and slightly slower.

 

But just to double check we are on the same page. This is totally artificial, right? console.log('SpriteRenderer.render', this) will output Sprite object instance in your code. So there's really no sense in creating it on SpriteRenderer.prototype, it would just create false impression that it belongs to SpriteRenderer instance scope. 

Link to comment
Share on other sites

Imagine this scenario though:

 

Say you've got a Sprite.render method with loads of code in. You create 100 sprites that don't modify the object shape at all. Perfect, zero cost. Imagine if you then create 100 more sprites that added a property to their own instances of Sprite. They've modified the shape of Sprite, so technically I assume have just invalidated themselves. Now this is where I was really wondering what happens under the hood - would these 100 'modified' objects now have their own copy of the render function, or would it still be a reference to the original prototype (assuming the render function itself wasn't changed).

 

Now image you create another 100 sprite objects and for some reason each one of them is given a new different property, making them all unique shapes. Have we now got another 100 copies of the render function, per object, floating around in memory?

 

While this shouldn't actually happen in practise it's not impossible (could be an easy newbie mistake for example). So really my very original question then boiled down to wondering if with a local render function would we now have consumed loads of memory for each object shape, or is it still smart enough not to do that?

Link to comment
Share on other sites

EDIT: Above my speculations, here's detailed explanation on how it _really_ works: http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation. It seems my speculations were not that far off. :)

 

Rich - first of all in no situations actual functions are copied. 

 

Second - Sprite.localRender function never exist on instance level. So irrespective of optimization, compiler does not need to be smart. They are simply never part of each of 1000 sprites (whether they are of the same hidden class or not). They have zero cost even on IE 6.0. ;) Only difference is that in v8 lookup of property first on object, then on prototype will be faster, as both places can be optimized with stubs. 

 

So in method 1 (prototype inheritance), irrespective of "hidden class optimizations", 1000 instances of sprite never have even "render" function property. This is very easy to test with hasOwnProperty. It will be false for sprite.hasOwnProperty("render"). They only have link to single prototype object (Sprite.prototype). 

 

For example, if you have an object Sprite with 3 numeric properties, and then 300 methods declared on Sprite.prototype, Sprite object instances will only store 3 numeric properties plus internal hidden pointer to Sprite.prototype. 

 

Discussion thread which I quoted above suggests, that optimization is done separately for objects and prototypes. If prototype functions are called frequently, they will be wrapped into their own hidden class. 

 

Here's a quote:

 

On Fri, Apr 16, 2010 at 5:24 PM, Anton Muhin <[email protected]> wrote: 
> Details are rather tricky overall, but roughly, yes, they cached: the 
> property is read or a function is called several times V8 looks for 
> its holder (object which owns the property) and compiles a stub that 
> first checks if any objects from receiver to holder has been changed 
> and if it's not the case, performs fast (sometimes really fast) 
> lookup. 

> If you really look for all the details, take a look at src/ic.cc and 
> looks into {Load,Store,Call}IC_Miss functions---they eventual call 
> UpdateCaches which could patch the code. 

> yours, 
> anton. 

Link to comment
Share on other sites

Right - bear with me, I'm struggling to find a different way to explain this :)

 

All objects have fixed shapes, right? Like within V8s internal dictionary. So if you're creating hundreds of identical objects it does it really quickly. But if you start modifying the instances for whatever reason I wonder at which point the internal cache/dictionary/whereever they are held gets invalidated and has to start storing each one uniquely, because it no longer fits a dictionary definition.

Link to comment
Share on other sites

And yes, it's always dangerous to try and second guess compilers. I'm just curious what they do internally when dealing with objects that reference other objects, which reference other objects, etc, etc - how far down the rabbit hole can the cache go :)

Link to comment
Share on other sites

I think what you are asking is covered in "Fast, in-object properties" in the article quoted above http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation

 

In short and possibly wrong explanation :), it creates a graph of object shapes discovered so far. And "transition" individual object between "shapes" as you add/remove properties (keeping offsets of properties that are already discovered). This is why it is important to keep initialization of variables in the same order if possible, to ensure Point will not not end up in two shapes (x, y and y, x).

 

Even beyond constructor, it tries to do that with each individual object as you play with it. But if you do that too much and it will suspect it is creating absurd amount of shapes, it will stop and swtich the object to simple slow Dictionary.

 

Now, related objects are not considered. They are optimized on their own. 

 

I do recommend reading the article set - it really added a lot to my understanding. For example, I was wrong to claim that functions in the approach suggested by @aerotwist will be stored on every instance. They are not. For functions V8 makes a guess that they will not change, and store them on the transition itself. If it is wrong, it creates new transition. Moreover, prototype and "own" methods use the same heuristics (constant function descriptors).

 

Overal conclusion on functions in particular: "As with regular objects, V8 will represent prototype methods using constant function descriptors. Calling prototype methods may be a little bit slower than calling "own" methods because the compiled code has to check not only the receiver's map but also the maps of its prototype chain. This probably won't make a measurable performance difference though and shouldn't impact the way you write code."

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...