Deleting an anonymous function

G

Gregor Kofler

What is the best practice for removing anonymous functions?

Something like


(function() { doSomething(); arguments.callee = null; })();

seems to work (at least it triggers no errors or exceptions on FF, but
is this really a working solution?

Gregor
 
D

David Mark

What is the best practice for removing anonymous functions?

Something like

(function() { doSomething(); arguments.callee = null; })();

seems to work (at least it triggers no errors or exceptions on FF, but
is this really a working solution?

You don't need to set arguments.callee to null. It is fine without
that line.
 
R

RobG

What is the best practice for removing anonymous functions?

Leave them for the garbage collector. Do you attempt to explicitly
remove named functions?

It seems to me that:

var foo = function(){ /*foo body */ };
foo();
foo = null;

results in the function formerly referred to as foo becoming anonymous
and, if there are no other references to it, it becomes available for
garbage collection.

It is, more or less, equivalent to:

(function(){ /*foo body */ })();


provided foo isn't called from elsewhere before being set to null.

Something like

(function() { doSomething(); arguments.callee = null; })();

seems to work (at least it triggers no errors or exceptions on FF, but
is this really a working solution?

It depends on what you interpret from the phrase "seems to work". The
arguments object belongs on to the execution context, it is given a
'callee' property that refers to the anonymous Function so that it can
be recursive. It has the property DontEnum, but is not specified as
being ReadOnly so you can set it to anything you like without causing
an error (in a browser conforming to ECMA-262, section 10.1.8).

Setting arguments.callee as a reference to some arbitrary object
doesn't seem to do anything useful and should have zero effect on when
the function is made available for garbage collection.
 
G

Gregor Kofler

David Mark meinte:
You don't need to set arguments.callee to null. It is fine without
that line.

To clarify that: An anonymous function gets removed by GC even if it's
content is "more complicated" including closures?

Something like:

(function() {
var counter = 0, id;
id = window.setInterval(function() {
document.write(counter++);
if(counter > 100) {
window.clearInterval(id);
}
}, 1000)
})();

Well, perhaps I'm just suffering some memory-leak-paranoia...

Gregor
 
T

Thomas 'PointedEars' Lahn

Gregor said:
David Mark meinte:

To clarify that: An anonymous function gets removed by GC even if it's
content is "more complicated" including closures?

Good question! An object should become marked for garbage collection (GC)
when there are no more references to it. The `arguments' object indeed has
this `callee' property which stores a reference to the Function object being
executed (ECMAScript Language Specification, Edition 3 Final, section 10.1.8).

However, although not explicitly stated so in the Specification, it stands
to reason that the `arguments' object usually ceases to exist when control
exits the execution context for the code of this function. Therefore, the
assignment to `arguments.callee' would be superfluous here with regard to GC.
Something like:

(function() {
var counter = 0, id;
id = window.setInterval(function() {
document.write(counter++);
if(counter > 100) {
window.clearInterval(id);
}
}, 1000)
})();

I am not sure about closures in function code and how they could keep the
corresponding Function object alive. One would first need to ascertain how
closures are maintained in an implementation in order to make a reasonable
statement about it. However, given the reasoning above, the assignment
arguments.callee = null; would appear to be irrelevant with regard to that
as well.
Well, perhaps I'm just suffering some memory-leak-paranoia...

I think your concern is certainly based on reasonable grounds.


PointedEars
 
G

Gregor Kofler

Thomas 'PointedEars' Lahn meinte:
I think your concern is certainly based on reasonable grounds.

I've just tried this on FF w/ Firebug:

for(var k = 100; --k;) {
(function() {
var stack = [], id, counter = 0;
id = window.setInterval(function() {
var s = [];
for(var j = 1000; j--;) {
s.push("xyz", "abc", "def");
}

stack.push(s);
console.log(counter);

if(++counter > 100) {
window.clearInterval(id);
}
}, 100)
})();
}

The memory usage of FF goes up from approx. 76MB to around 290MB, then
(after clearing the console) to 77MB, a reload pushed it again to 290MB
before returning to (hooray!) 76MB.

It's of course a bit pedestrian, but the GC seems to collect all the
unused stuff properly.

Gregor
 
R

Richard Cornford

Thomas said:
Good question! An object should become marked for
garbage collection (GC) when there are no more
references to it. The `arguments' object indeed has
this `callee' property which stores a reference to
the Function object being executed (ECMAScript
Language Specification, Edition 3 Final, section
10.1.8).

However, although not explicitly stated so in the
Specification, it stands to reason that the `arguments'
object usually ceases to exist when control exits the
execution context for the code of this function.
Therefore, the assignment to `arguments.callee' would
be superfluous here with regard to GC.


I am not sure about closures in function code and how
they could keep the corresponding Function object
alive. One would first need to ascertain how closures
are maintained in an implementation in order to make a
reasonable statement about it. However, given the
reasoning above, the assignment
arguments.callee = null; would appear to be irrelevant
with regard to that as well.

It might be possible to answer this by observing the behaviour of the
implantations rather than studying their (not always available) source
code. The thing that a closure must always preserve (or behave as if it
were preserving) is the Variable object. Variable objects are created
for execution contexts, as are - arguments- objects, but the inner
functions created in an execution context keep references to the
execution context's variable object (if they can be referenced from
outside that execution context (if there is a closure)). And so if the
Variable object keeps its reference to the corresponding - arguments -
object and that arguments object keeps its - callee - reference then in
principle a reference to the outer function is preserved.

However, the variable object's - arguments - property is unusable after
the end of the execution context for which it was created because any
execution context for an inner function will have its own Variable
object with its own - arguments - property, and so any - arguments
properties of any other Variable objects on the scope chain will be
masked by the one on the most local Variable object. Thus upon leaving
an execution context it would be perfectly viable for an implementation
to clear the - arguments - property of a variable object (even to remove
the property from the object), because from that point on it is
inaccessible anyway.

If they don't clear/remove the- arguments - property, and/or don't'
clear the - callee - property on leaving the execution context then the
closure will preserve the reference to the outer function object.

The 'trick' to exposing some truths of this will be to explicitly
preserver the - arguments - objects in the closures by assigning them to
local variables of the execution context in which the closure forming
function is created. These local variables will not be masked and thus
their references to the - arguments - objects cannot be cleared.

This would allow an examination of the preserved arguments objects,
through their variable references, to see if their - callee - properties
do retain their references to the original outer function. But of more
immediate interest would be the parallel testing of the two structures
to see if their memory use differed significantly. To create, say,
100,000 closures in the normal way, and then create 100,000 where the -
arguments - object was explicitly preserved. If the increase in memory
use after the second test (including giving the garbage collector time
to catch up) was not higher then the first then you might conclude that
the normal operation was preserving the - arguments - objects at the
very least. If the second test showed significantly increased memory use
then it would seem that the normal operation was not (needlessly)
preserving the - arguments - objects and that there is no issue here (at
least where that particular implementation was concerned).

In the even of discovering that there was an issue it would the be
possible to test the second option against a variation where -
arguemtns.calleee - was explicitly set to null in order to see if that
made any difference.
I think your concern is certainly based on reasonable
grounds.

Given the increase in AJAX projects where the intention is not to
navigate way form a 'page' for long periods of time and the increased
use of (less then optimally implemented) functional programming
structures (particularly in 'popular' libraries) it does seem reasonable
to gauge the extent to which this is an issue.

So to get the ball rolling; testing XP 64, 64 bit IE 6, quad core Intel
processor, 4 Gigabytes physical memory:-

Test A:-

var ar = [];
for(var c = 0;c < 100000;++c){
ar.push(
(function(x){
return (function(y){
x = y;
});
})()
);
}
------------------------------------------------------------------
Test B:-

var ar = [];
for(var c = 0;c < 100000;++c){
ar.push(
(function(x){
var args = arguments; //<-- The explicit preserving of
// the arguments object.
return (function(y){
x = y;
});
})()
);
}
------------------------------------------------------------------
Test C:-

var ar = [];
for(var c = 0;c < 100000;++c){
ar.push(
(function(x){
var args = arguments; //<-- The explicit preserving of
// the arguments object.
arguments.callee = null; //<-- The explicit clearing of
// the - callee - reference
return (function(y){
x = y;
});
})()
);
}
------------------------------------------------------------------

Executing these scripts in one page and then navigating to an
"about:blank" homepage and recording the defences in memory use between
the two pages loaded states, and repeating a few times, the results
were:-

For test A the difference between "about:blank" loaded and the post-test
code execution of the test page were; ~76 Megabytes more for the test
script.

Test B; ~145 Megabytes

Test C; ~128 Megabytes

Conclusion: In the environment tested (JScript) the normal process does
not raise an issue. If, however, the - arguments - object is explicitly
preserved then the memory consumption does increase significantly, and
given that explicitly clearing the - arguments.callee - in test C did
decrease the extent of that memory increase it would be reasonable to
conclude that some of that memory increase (~17 Megabytes) is accounted
for by now non-garbage collectable outer function objects.

It is not possible to conclude from these test alone that in the normal
circumstances JScript is clearing the Variable object's - arguments -
reference because it may be the case that the act of assigning -
arguments - to a variable results in a new 'wrapper' object being
created at that point and it being the creation of that object that
increases the memory use. (Consider, for example, that all references to
window.event - in IE return a distinct object instead of multiple
references to a single event object.)

A quick test of not preserving the - arguments - obejct in a variable
but still clearing arguments.callee - showed the same memory use as Test
A.

If nobody else gets there first I will try out similar tests on other
browsers soonish.

Note: when testing other browsers it will be worth considering that some
preserve an 'in memory' copy of pages in their recent history (Opera and
Firefox at minimum) so the methodology of navigating between a 'test'
page and an 'about:blank' page will not be appropriate.

Richard.
 
G

Gregor Kofler

Richard Cornford meinte:

[something I'll try to grasp later]
Given the increase in AJAX projects where the intention is not to
navigate way form a 'page' for long periods of time and the increased
use of (less then optimally implemented) functional programming
structures (particularly in 'popular' libraries) it does seem reasonable
to gauge the extent to which this is an issue.

I idea is some "flash-once" highlighting of elements on the page; a
setTimeout/setInterval that does some color or opacity manipulation
packaged in an anonymous function - fire and forget. However, in this
very application one could accumulate quite a few of those highlighters.
Executing these scripts in one page and then navigating to an
"about:blank" homepage and recording the defences in memory use between
the two pages loaded states, and repeating a few times, the results were:-

For test A the difference between "about:blank" loaded and the post-test
code execution of the test page were; ~76 Megabytes more for the test
script.

Test B; ~145 Megabytes

Test C; ~128 Megabytes

Conclusion: In the environment tested (JScript) the normal process does
not raise an issue. If, however, the - arguments - object is explicitly
preserved then the memory consumption does increase significantly, and
given that explicitly clearing the - arguments.callee - in test C did
decrease the extent of that memory increase it would be reasonable to
conclude that some of that memory increase (~17 Megabytes) is accounted
for by now non-garbage collectable outer function objects.

Thanks for testing - very interesting indeed.
If nobody else gets there first I will try out similar tests on other
browsers soonish.

Greatly appreciated.

Gregor
 
R

Richard Cornford

Gregor said:
Thomas 'PointedEars' Lahn meinte:
I think your concern is certainly based on reasonable
grounds.

I've just tried this on FF w/ Firebug:

for(var k = 100; --k;) { (function() {
var stack = [], id, counter = 0;
id = window.setInterval(function() {
var s = [];
for(var j = 1000; j--;) {
s.push("xyz", "abc", "def");
}

stack.push(s);
console.log(counter);

if(++counter > 100) {
window.clearInterval(id);
}
}, 100)
})();
}
The memory usage of FF goes up from approx. 76MB to around
290MB, then (after clearing the console) to 77MB, a reload
pushed it again to 290MB before returning to (hooray!) 76MB.

It's of course a bit pedestrian, but the GC seems to collect
all the unused stuff properly.

This is a test for a memory leak, and not finding one is good. But I
don't think anyone was expecting to find a memory leak here anyway.
Circular chains of references between pure javascript objects have never
been a garbage collection issue once there are no external references to
any of the objects involved.

You have shown ~2 Megabytes per loop iteration of memory consumption,
and those of us who used 1980's computers, where having 2 Megabytes of
memory would have been unusual, may find that a little shocking. For an
in-page AJAX web application the important question may not only be the
overall garbage collection effectives but also the memory footprint of
the running application, so if that per iteration memory consumption
could be reduced by freeing more objects sooner that may be significant.
You have shown that the outer function gets garbage collected
eventually, but not that it can be garbage collected as soon as its
execution context is finished.

Richard.
 
R

Richard Cornford

Gregor said:
Richard Cornford meinte:

Thanks for testing - very interesting indeed.
<snip>

Looking at this on Firefox and Opera (various recent versions) produced
pretty much the same pattern as IE. So there is no need to worry about
closures needlessly preserving references to outer function objects
through - arguments/arguments.callee - in those environments either.

Windows Safari was more of a problem as the test code used for Opera and
Firefox killed its ability to execute javascript (strangely, as it was
quite happy to carry on displaying HTML pages and navigate, etc. it just
would not execute any more javascript of any sort until shut down and
re-started). It seems that the Safari's issue related directly to the
number of closures being formed (the number of iterations in the loops).
Unfortunately reducing the length of the loops to the point where Safari
could survive the test code resulted in the changes in memory use
measured for the tests being too small to be conclusive.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,141
Messages
2,570,813
Members
47,357
Latest member
sitele8746

Latest Threads

Top