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:
1.) Declare "top-level" vars for index and length; reuse them everywhere
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.
2.) If array length is known, don't loop -- unroll
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?
3.) Leverage closures, via the factory pattern
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.
varallocates memory that the GC cares about? Even in a simple and stupid stack-based bytecode VM like CPython, the local variables are a native array in the frame object and never allocated separately or added/removed while a function is running.withandeval, a function's execution context (a type of frame object) can survive after the function returns. An EC that must survive will be allocated on the heap, but exactly when/how this happens depends on the engine. (Clever engines like V8 will optimize, using the stack for local vars -- only copying context vars to the heap when they must outlive the function call). If/when these heap objects become unreferenced/inaccessible, they will be GC'd.var. In the absence ofeval/with(which can be determined statically, or assumed and checked at runtime) it's trivial to just allocate space for all local variables in the EC when you create it, so individualvars don't need any allocation.function(a, b, returnValue)pattern to avoid allocations. Also see this article on writing low-garbage JS.