Have you ever listen to someone says:
don't stress the garbage collector
???
But, what it means?
The theory of the Node.js Garbage Collector has been broadly described on the web. I think this article by Vincent Vallet it is the best one.
But, here you are a practical explanation!
Create a new stress.js
file with this simple code:
function doSomeThing () {
const s = `${'a'.repeat(10e6)}-${'v'.repeat(10e6)}`
return s
}
setInterval(doSomeThing, 10)
and run it with node --trace-gc stress.js
You will see an endless output like this:
[8904:0000018D2126F130] 39 ms: Scavenge 2.1 (3.3) -> 1.7 (4.3) MB, 1.5 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8904:0000018D2126F130] 1302 ms: Scavenge 2.4 (4.8) -> 2.0 (4.8) MB, 6.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8904:0000018D2126F130] 5992 ms: Scavenge 2.9 (4.8) -> 2.0 (5.3) MB, 2.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8904:0000018D2126F130] 10989 ms: Scavenge 3.0 (5.3) -> 2.0 (5.3) MB, 0.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[8904:0000018D2126F130] 14104 ms: Mark-sweep 2.6 (5.3) -> 1.6 (4.3) MB, 4.2 / 0.0 ms (+ 0.1 ms in 2 steps since start of marking, biggest step 0.1 ms, walltime since start of marking 10 ms) (average mu = 1.000, current mu = 1.000) finalize incremental marking via task GC in old space requested
[8904:0000018D2126F130] 14730 ms: Mark-sweep 1.7 (4.3) -> 1.6 (4.3) MB, 0.5 / 0.0 ms (+ 0.5 ms in 4 steps since start of marking, biggest step 0.3 ms, walltime since start of marking 23 ms) (average mu = 0.998, current mu = 0.998) finalize incremental marking via task GC in old space requested
[8904:0000018D2126F130] 20152 ms: Scavenge 2.6 (4.3) -> 1.6 (4.3) MB, 0.2 / 0.0 ms (average mu = 0.998, current mu = 0.998) allocation failure
...
The Garbage Collect Scavenge process is run many times every ~5 seconds! Forever!!
What is happening under the hood?
- a big string
s
is allocated and added to the New Space - the
s
string is released - a new big string
s
is allocated and added to the New Space - the
s
string is released - another new big string
s
is allocated and added to the New Space - the
s
string is released - the Heap "New Space" is full, so the Garbage Collector Scavange phase starts and clean all the
s
allocations - restart from the beginning
Now fix the script moving the s
variable in the parent scope and run the changed file node --trace-gc no-stress.js
:
const s = `${'a'.repeat(10e6)}-${'v'.repeat(10e6)}`
function doSomeThing () {
return s
}
setInterval(doSomeThing, 10)
The output is completely different:
- only 2 calls to Scavange
- only 2 Mak-sweep
in a 15 minutes run!!
[16388:000002874922BD50] 33 ms: Scavenge 2.1 (3.3) -> 1.7 (4.3) MB, 1.2 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[16388:000002874922BD50] 9338 ms: Scavenge 2.4 (4.8) -> 2.0 (4.8) MB, 4.7 / 0.0 ms (average mu = 1.000, current mu = 1.000) allocation failure
[16388:000002874922BD50] 18877 ms: Mark-sweep 2.2 (4.8) -> 1.6 (4.8) MB, 3.6 / 0.0 ms (+ 0.8 ms in 3 steps since start of marking, biggest step 0.7 ms, walltime since start of marking 17 ms) (average mu = 1.000, current mu = 1.000) finalize incremental marking via task GC in old space requested
[16388:000002874922BD50] 19484 ms: Mark-sweep 1.6 (4.8) -> 1.6 (5.3) MB, 1.7 / 0.0 ms (+ 1.1 ms in 2 steps since start of marking, biggest step 1.1 ms, walltime since start of marking 3 ms) (average mu = 0.995, current mu = 0.995) finalize incremental marking via task GC in old space requested
What is happening under the hood?
- a big string
s
is allocated and added to the heap space - the
s
string survive to two Scavange's generations and goes to Old Space - no more stress on the Garbage Collector!
This means that you may optimize your long-running Node.js process reusing variables and functions instead of generating new ones every time in your hot path code like a validation function for every HTTP request that executes a RegExp!
So, don't stress the Garbage Collector!
Top comments (0)