The hidden cost of temporally allocated objects in JavaScript
Published: 2023-11-16 20:35:13

---
title: The hidden cost of temporally allocated objects in JavaScript
published: true
date: 2023-11-16 20:15:22 UTC
tags: 
canonical_url: https://gabrielrurbina.hashnode.dev/the-hidden-cost-of-temporally-allocated-objects-in-javascript
---

If JavaScript is single-threaded then running this code with one core or with several should make no difference, or should it?

```
const syntheticOp = ({ a, b }) => {
    let res = [];
    for (let i = 0; i < 200000 ; i++) {
        res[i] = { val: a * b * BigInt(i) };
    }
    return res;
};

function main(){
    for (let i = 0; i < 100; i++) {
        syntheticOp({
            a: BigInt(Math.floor(Math.random() * 1000)),
            b: BigInt(Math.floor(Math.random() * 1000)),
        });
    }
}
main();

```

Let's run it with one core `taskset -c 15 node --inspect-brk synthetic.js` and profile it.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700159281415/7052591b-0253-41d5-8876-d6e2403ecdd6.png)

The whole operation took over 7 seconds and most of the time (60%) of the time was taken by garbage collection pauses, now we run it with 4 cores `taskset -c 12,13,14,15 node --inspect-brk synthetic.js` and see what the profiler tells us.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700159368130/782aa34d-bf1b-4ed4-aef6-06571b8e69cd.png)

Hmm, fascinating, why is the 4 cores execution near 2x faster than the single core execution?, and why the speed up is mostly on the garbage collector, that no longer takes most of the execution time? well, we do know why; **Java-script is single-threaded, its garbage collector is not** , [see V8's Orinoco](https://v8.dev/blog/trash-talk), so the performance gain is on the back of the garbage collector, this is great isn't?, it is, as long as the developer is counting on deploying its application on a multi-threaded environment, and we rarely do.

The more objects we allocate, the more GC will take time to collect, slowing down our application/program, effectively turning object allocations into a processing power loan that have to pay back on the GC pauses, somewhat of an oversimplification, but good enough to understand the price we pay for memory allocation.

You might be interested on a concrete case, not just a synthetic test, so lets take a look at my library [type-guard](https://github.com/gabrielricardourbina/type-guard) on version version _0.2.2_, benchmark it with large objects and inspect memory and execution profiles.

```
import {
    ObjectOf, isString, isNumber, ArrayOf
} from "@gabrielurbina/type-guard";

const persons = Array.from({ length: 100000 }, () => ({
    firstName: Math.random().toString(36).slice(2),
    lastName: Math.random().toString(36).slice(2),
    age: Math.floor(Math.random() * 100),
}));

const isPersons = ArrayOf([
    ObjectOf({
        firstName: isString,
        lastName: isString,
        age: isNumber,
    }),
]);

function main() {
    for (let i = 0; i < 100; i++) {
        isPersons(persons);
    }
}

main();

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700159567560/0541ca3c-d73e-43e9-9fad-77a2cbf85d69.png)

1.4% of GC usage is great, isn't,well it could be better, let's have a look at how much memory are we creating here

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700159745977/62d5fc97-f1ac-403e-b843-6c55dec2579a.png)

Wow, we are creating a few millions objects during the execution of this benchmark, that can definitely be improved, and I already did on version [version 0.2.3](https://github.com/gabrielricardourbina/type-guard/commit/3de9bfe226a60ddf8090f0da2c9b64190459c979) where I dropped all temporally allocated objects, and if we inspect the same metrics for this version you might be surprised.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700159651258/8558d185-c68e-4959-a88c-5d4e2f1fd3d1.png)

During the execution of the benchmark, there was no GC pause, which is phenomenal, also this version is close to 2x faster, but how about the memory usage?

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700159692530/88762296-ae56-4f9b-ab84-dfd986e55b46.png)

Now we are talking, I managed to reduce the memory usage from few millions objects to near a hundred objects, also to have a constant memory usage over time, after initialization, the memory usage does not increase, _making this library perfectly suitable for running on single-core applications and for indefinitely long processes_.

Buy why, why does it matter, these are just some extra ms of runtime and some extra Megabytes of memory? Because I just want to ship non-pessimized code, I also recognize that the state of the JavaScript ecosystem performance is as poor as it is, not because one single master library, but because each ms and MB not optimized adds up to our already sluggish ecosystem.