Reputation: 3972
Say we're writing a browser app where smooth animation is critical. We know garbage collection can block execution long enough to cause a perceptible freeze, so we need to minimize the amount of garbage we create. To minimize garbage, we need to avoid memory allocation while the main animation loop is running.
But that execution path is strewn with loops:
var i = things.length; while (i--) { /* stuff */ }
for (var i = 0, len = things.length; i < len; i++) { /* stuff */ }
And their var
statements allocate memory can allocate memory that the garbage collector may remove, which we want to avoid.
So, what is a good strategy for writing loop constructs in JavaScript that avoid allocating memory each one? I'm looking for a general solution, with pros and cons listed.
Here are three ideas I've come up with:
We could declare app.i
and app.length
at the top, and reuse them again and again:
app.i = things.length; while (app.i--) { /* stuff */ }
for (app.i = 0; app.i < app.length; app.i++) { /* stuff */ }
Pros: Simple enough to implement. Cons: Performance hit by dereferencing the properties might mean a Pyrrhic victory. Might accidentally misuse/clobber properties and cause bugs.
We might be guaranteed that an array has a certain number of elements. If we do know what the length will be in advance, we could manually unwind the loop in our program:
doSomethingWithThing(things[0]);
doSomethingWithThing(things[1]);
doSomethingWithThing(things[2]);
Pros: Efficient. Cons: Rarely possible in practice. Ugly? Annoying to change?
Write a factory function that returns a 'looper', a function that performs an action on the elements of a collection (a la _.each
). The looper keeps private reference to index and length variables in the closure that is created. The looper must reset i
and length
each time it's called.
function buildLooper() {
var i, length;
return function(collection, functionToPerformOnEach) { /* implement me */ };
}
app.each = buildLooper();
app.each(things, doSomethingWithThing);
Pros: More functional, more idiomatic? Cons: Function calls add overhead. Closure access has shown to be slower than object look-up.
Upvotes: 8
Views: 1423
Reputation: 13709
And their var statements allocate memory can allocate memory that the garbage collector may remove, which we want to avoid.
This is slightly misinformed. Simply using var
does not allocate memory on the heap. When a function is called, each variable used in the function is allocated in advance on the stack. When the function completes execution, the stack frame is popped and the memory is immediately dereferenced.
Where garbage collection-related memory concerns become a problem is when you're allocating objects on the heap. That means any of the following:
For the most part, anything where typeof foo
returns "function"
or "object"
(or any of the new ES6 typeof
return values) will generate an object on the heap. There's probably more that I can't think of right now.
The thing about objects on the heap is that they can refer to other objects on the heap. So for instance:
var x = {};
x.y = {};
delete x;
In the example above, the browser simply can't deallocate the slot for x
, because the value contained within it is of variable size. It lives on the heap, where it could then point to other objects (in this case, the object at x.y
). Another possibility is that there's a second reference to the same object:
var x = {};
window.foo = x;
delete x;
The browser simply can't remove the object at x
from memory, since something else is still pointed at it.
So long story short, don't worry about removing variables, because they work perfectly well and are totally performant. Heap allocations are the real enemy when it comes to garbage collection, but even a few small heap allocations here and there won't hurt most apps.
Upvotes: 2