Memory Allocation and Garbage Collection in Javascript

Memory Allocation
This is how we can allocate memory – by taking photos. This empty frame is ready to be filled with a memory. Photo by Rolands Zilvinskis on Unsplash

Javascript developers usually don’t care much about memory management. The JS engine does most of the heavy lifting for us.

In this post I’m going to demonstrate how important it is to understand JavaScript memory management.

Let’s take a look at an example:

function buildArray(n) {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(i);
}
}
function buildArray2(n) {
const arr = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
arr[i] = (i);
}
}

The two functions – buildArray and buildArray2 do the same thing. They iterate n times and add the current index value into an array in the index position.

I beg to differ

The difference between the two functions is in the array initiation line:

The difference is marked with the red circles.

Measuring the runtime of these functions with 20000000 array members shows us that buildArray2 is much faster (figure 1).

Figure 1: buildArray run time (top row) vs. buildArray2 run time (bottom row)

You’d might say that 20000000 is not a valid number of array members. You are correct in most cases. You should remember, though, that this is a very simple example in which the array members are integers.

In addition, you’d might have a process that runs periodically. The difference between the above functions, multiplied by the time your process repeats can become substantial as you scale (a rendering cycle for instance).

I’ve prepared a live demo with which you can play with the size of the array as well as the number of times you repeat the iteration: https://yonatankra.com/performance/memory/demos/gcAndAllocation

If we use the demo above to test our code and set our arrays to 1000 members, with 20000 iterations we see the results in figure 2. These results show us that even with relatively small arrays, we’d might get a performance hit in certain cases.

Figure 2: The results of 20000 iterations with 1000 members array

For instance, imagine a function that recreates our small array for immutability. This function might run in response a user’s click. It can also run in response to socket messages that can come rapidly.

The function done without pre-allocation will take less time to run – and the difference will stack.

Cause and Effect

In order to understand the cause for these differences, we can profile using the chrome performance tool.

The results of the profiling of the 20000000 array members run is shown in Figure 3.

Figure 3: a) Profiling of buildArray. Bottom yellow rectangles are garbage collection. b) Profiling of buildArray2.

Figure 3a shows a long runtime with several garbage collection (GC) cycles. Multiple garbage collections are an indication for massive and inefficient memory allocation. Garbage collection happens when the JS engine has some data it does not need anymore and needs to dispose of it.

In our case, what happens is that the function pushes more and more elements into the array. Javascript needs more space for the array, and thus allocates more memory every few iterations.

Figure 4 shows that pushing to the array took much more time per iteration than just changing a value in a pre allocated array. That’s because there was no need to allocate memory during the run.

Figure 4: Time spent in each line in the functions (for lines that took more than 0.1ms). Pre allocating the array took less time than pushing into the array.

Because we are using an array, the JS engine tries to optimize by maintaining the array as a C++ array. For this, the array must be contiguous in memory.

Because of that, the JS engine keeps allocating and reallocating memory for the whole array every time – and needs to dispose of the old chunks of memory in the “old place”. This is the garbage collection we see.

Figure 3b shows that if we preallocated the memory beforehand, the JS engine already did the allocation when we started and hence does not need to do the whole reallocation and garbage collection as we go.

What we also learn here is this: if our array size is relatively small and we do not repeat this process a lot, then preallocation might be less performant since the pre-allocation at the beginning will be very costly.

Summary

In this article, we learned what is memory allocation and garbage collection.

Memory allocation is the process in which the software (in this case, the JS engine) finds room in memory for variables. Garbage collection is the process in which the JS engine removes obsolete data from memory.

Both descriptions here were brief, as the main point was to actually be able to see the effects of the allocation and GC.

Of course, these two terms that usually go hand in hand have a lot more under the hood and you can read more about them.

I recommend this really nice article about garbage collection.

You can play around with the demo and see the results for yourself.

Quick question: set the Array Size to 1000 and the iterations to 1 and look at the result in the console. What do you see? Can you explain the result after what we’ve learned?

Sign up to my newsletter to enjoy more content:

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments