sienki_jenki
sienki_jenki

Reputation: 13

Javascript memory allocation while passing objects to functions

recently I had problem with long minor and major gc sweeps in my app, overall I was allocating too much memory too quickly. But it also got me thinking on such case: Often when there are multiple arguments to function I'm creating an object to not mess up order and I'm wondering how much does it affect minor and major gc sweeps. Consider 2 scenarios:

  1. const fn = (arg1, arg2, arg3, arg4) => {...}
  2. const fn = ({ arg1, arg2, arg3, arg4 }) => {...}

Subtle change, but in first example we are not allocating any memory right? All of the variables are already defined and have memory allocated for them. In the second example if we want to call this function, we have to create object therefore allocate memory.

So I was wondering if I'm calling 2nd function very quickly, like every few ms, is it making garbage collector sweeps a lot longer, or maybe it's negligible? Does it even allocate memory or JS engines are taking care of that and it's basically free as in 1st example?

Upvotes: 1

Views: 149

Answers (2)

Kirill Kay
Kirill Kay

Reputation: 901

This is a very interesting question (and thanks to @jmrk for in-depth answer), but I wanted to add something from a practical standpoint, as recently it bit me with Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory due to passing quite large object as a function argument using 2nd method (inside an object of params), and the only thing that saved me from heap out of memory was switching to the first method.

Detailed example, consider same function called in two ways (similar to the original question):

[...]
const veryLargeDataObject = {...}

// option 1
for (const step of steps) {
 process({ param1, param2, ...config}, veryLargeDataObject) // veryLargeDataObject is a separate param
}

// option 2
for (const step of steps) {
 process({ param1, param2, ...config, veryLargeDataObject}) // veryLargeDataObject is inside object with all other params
}

So, while in theory (if I understood docs and @jmrk's answer correctly), both calls should not create any veryLargeDataObject clones and GC would handle this just fine. In reality however, option 2 consistently lead to heap out of memory crash as soon as the for...of loop was called enough times to fill all allocated memory (~20 loops in my case). Switching to option 1 completely fixed the situation and speed up the loop significantly.

Hopefully it helps someone who encounters heap overflow due to unintentional object cloning.

Upvotes: 0

jmrk
jmrk

Reputation: 40641

(V8 developer here.)

in first example we are not allocating any memory right?

Right.

if I'm calling 2nd function very quickly, like every few ms, is it making garbage collector sweeps a lot longer, or maybe it's negligible?

Should be negligible: an object every few milliseconds is no problem for the garbage collector. In particular, these objects should be very short-lived (unless you're storing references to them somewhere), so they should never make it to the old generation, and hence won't affect major GC cycles.

If you wanted to execute this call hundreds of times per millisecond, there might be more reason to have performance concerns, but see the point below.

Does it even allocate memory or JS engines are taking care of that and it's basically free as in 1st example?

Depends.
Generally speaking, you're creating an object there, and that's an allocation.
However, if that call is very hot, then the calling function will get optimized eventually, and the callee might get inlined, and after inlining the parameters object might get optimized out (by a technique often called "escape analysis" in the optimizing compiler). In that case, the result will be as efficient as the first example.

Upvotes: 1

Related Questions