10

I was just wondering how the overhead is on a function object.

In an OOP design model, you can spawn up a lot of objects each with their own private functions, but in the case where you have 10,000+, these private function objects, I assume, can make for a lot of overhead.

I'm wondering if there are cases where it would be advantageous enough to move these functions to a utility class or external manager to save the memory taken up by these function objects.

15
  • 8
    the js engine can recycle identical function objects, so 10,000 function vars in user land can really be 1 function and 10,000 pointers... Commented Jun 25, 2013 at 22:11
  • 2
    Are you planning on using the utility class? It’ll take up even more memory… anyways, this a) depends on the engine and b) is probably not something to worry about unless you see it become a problem. Commented Jun 25, 2013 at 22:11
  • My guess is that the interpreters optimize this fairly well, but I'm sure it's possible to write terrible code. Is there a way to audit JS memory usage? Commented Jun 25, 2013 at 22:11
  • 2
    @landons: chrome has a task manager + profiles + timeline in dev tools to watch JS ram usage. Commented Jun 25, 2013 at 22:12
  • 1
    @dandavis: Can you provide a pointer to the specs where recycling of the full function object is permitted? That would make things like x = function(){...}; x.foo = []; a no-no and ECMA docs are for me as readable as an encrypted file. Commented Jun 26, 2013 at 8:27

3 Answers 3

11

This is how Chrome handles functions, and other engines may do different things.

Let's look at this code:

var funcs = [];
for (var i = 0; i < 1000; i++) {
    funcs.push(function f() {
        return 1;
    });
}
for (var i = 0; i < 1000; i++) {
    funcs[0]();
}

http://jsfiddle.net/7LS6B/4/

Now, the engine creates 1000 functions.

The individual function itself takes up almost no memory at all (36 bytes in this case), since it merely holds a pointer to a so-called SharedFunctionInfo object, which is basically a reference to the function definition in your source code*. This is called lazy parsing.

Only when you run it frequently does the JIT kick in, and creates a compiled version of the function, which requires more memory. So, funcs[0] takes up 256 bytes in the end:

Heap analyzer screenshot

*) This is not exactly true. It also holds scope information and the function's name and other metadata, which is why it has a size of 592 bytes in this case.

Sign up to request clarification or add additional context in comments.

3 Comments

Is the JIT-compiled function shared as well (does funcs[1] run the same compiled code or does it need to get compiled on its own)?
@Bergi Good point. I don't know what happens with a single run. However, if you run funcs[1] a 1000 times (while funcs[0]'s compiled version is cached), then the analyzer shows that funcs[1] is getting added a reference to funcs[0] (not directly to the compiled version). Not sure how that works, but looks like there's some magic involved there
The 36 bytes for function identity (72 under 64-bit node) is actually a lot and in fact the deal breaker. It is 3 times more than normal object overhead (12 bytes or 24 bytes on 64-bit). Assuming node64, and if you create 200 objects, that each have 25 methods and you have 20 requests per second, you create ~7 megabytes of literal garbage per second and put insane pressure on the GC and waste a lot of a server potential. And of course on the client side if you make games or rich GUI apps you will have terrible UX from the pauses.
4

First of all, it's common to place methods in the object constructor prototype, so they'll be shared among all instances of a given object:

function MyObject() {
    ....
}

MyObject.prototype.do_this = function() {
   ...
}

MyObject.prototype.do_that = function() {
   ...
}

Also note that a "function object" is a constant code-only block or a closure; in both cases the size is not related to the code:

x = [];
for (var i=0; i<1000; i++) {
    x.push(function(){ ... });
}

The size of each element of the array is not going to depend on the code size, because the code itself will be shared between all of the function object instances. Some memory will be required for each of the 1000 instances, but it would be roughly the same amount required by other objects like strings or arrays and not related to how much code is present inside the function.

Things would be different if you create functions using JavaScript's eval: In that case I'd expect each function to take quite a bit and proportional to code size unless some super-smart caching and sharing is done also at this level.

6 Comments

You should mention that each function needs to hold references to the scoped vars, so there is some (minor) difference depending on that.
@Pumbaa80 - no, the function object doesn't hold that info, which can be late collected as needed anyway since closure prevent garbage collection.
@Pumbaa80: in the example all functions are going to share the same closed over variables because the scope is in Javascript is the whole function. Things would be different for example with for(var i=0; i<1000; i++){ x.push((function(i){return function(){alert(i)}}))(i)); } where each closure would reference a different i variable.
To be more precise, I'm talking about the Environment Record, which has to be stored alongside the function. Now how much memory does it take? Actually, I'm not aware of the implementation details of any engine, but after thinking about it, I guess there may just be a pointer to some Lexical Environment object (in which case there would be no difference between functions).
I guess one way to put this simply is: Your JS environment will have a number of function objects equal to the number of times the keyword 'function' is written in your code (or perhaps for some intelligent compilers, less than that)
|
2

Function objects do in fact take up a lot of space. Objects themselves may not take up much room as shown below but Function objects seem to take up considerably more. In order to test this, I used Function("return 2;") in order to create an array of anonymous functions.

The result was as implied by the OP. That these do in fact take up space.

Created Function

100,000 of these Function()'s created caused 75.4 MB to be used, from 0. I ran this test in a more controlled environment. This conversion is a little more obvious, where it indicates that each function object is going to consume 754 bytes. And these are empty. Larger function objects may surpass 1kb which will become significant very quickly. Spinning up the 75MB was non trivial on the client, and caused a near 4 second lock of the UI.

Here is the script I used to create the function objects:

fs = [];
for(var i = 0; i < 100000; i++ ){
 fs.push(Function("return 2;"));
}

Calling these functions also affects memory levels. Calling the functions added an additional 34MB of memory use.

Called Called

This is what I used to call them:

for( var i = 0; i < fs.length; i++ ){
 for( var a = 0; a < 1000; a++ ){
     fs[i]();
 }
}

Using jsfiddle in edit mode is hard to get accurate results, I would suggest embedding it.

Embedded jsFiddle Demo


These statements are incorrect, I left them to allow the comments to retain context.

Function objects don't take very much space at all. The operating system and memory available are going to be what decides in the end how this memory is managed. This is not going to really impact anything on a scale which you should be worried about.

When loaded on my computer, a relatively blank jsfiddle consumed 5.4MB of memory. After creating 100,000 function objects it jumped to 7.5MB. This seems to be an insignificant amount of memory per function object (the implication being 21 bytes per function object: 7.5M-5.4M / 100k).

memory image

jsFiddle Demo

17 Comments

It's not quite that simple: the amount of memory used by function objects will increase as they are compiled in any JITing VM, possibly in multiple steps (depending on whether multiple compiled copies are kept at the same time).
@gsnedders - "The amount of memory used by function objects will increase as they are compiled". And how long do you think it takes to compile? I took the screenshot above and cropped it for brevity. It remained at the same level of 7.5MB for quite some time until it actually dropped down after several minutes. It never increased. What basis or evidence do you have for your comment?
start calling them. And in that Fiddle you only ever have one function object (f) — you just call [[Construct]] on it 100k times, which will create (non-function) objects.
@gsnedders - Thank you for these comments. Although calling them did not affect memory levels, you were certainly right about the difference from instantiated functions (plain objects) and function objects. Please see my edit above.
Your answer is only valid for JS engines with lazy parsing
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.