Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern for no-allocation loops in JavaScript?

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.

like image 297
GladstoneKeep Avatar asked Aug 24 '13 18:08

GladstoneKeep


People also ask

How do I force garbage collection in JavaScript?

Garbage collection is performed automatically. We cannot force or prevent it. Objects are retained in memory while they are reachable. Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above.

What algorithm causes memory leak in JavaScript?

setTimeout and setInterval are the two timing events available in JavaScript. The setTimeout function executes when the given time is elapsed, whereas setInterval executes repeatedly for the given time interval. These timers are the most common cause of memory leaks.

Does JavaScript have memory allocation?

In contrast, JavaScript automatically allocates memory when objects are created and frees it when they are not used anymore (garbage collection). This automaticity is a potential source of confusion: it can give developers the false impression that they don't need to worry about memory management.

Is garbage collection automatic in JavaScript?

JavaScript automatically collects the information of the unmercenary memory blocks and removes them from the memory. The garbage collector searches for reachability from the root object to determine whether an object will be used in the future or not.


1 Answers

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:

  • Closures
  • Event listeners
  • Arrays
  • Objects

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.

like image 58
mattbasta Avatar answered Oct 04 '22 11:10

mattbasta