JavaScript memory usage benchmark
New to topics? Read the documentation here!
In this section we will use the file nodejs/bench_mem.js, tests are run on Node.js v16.14.2 from NVM, Ubuntu 21.10, on Lenovo ThinkPad P51 (2017) which has 32 GB RAM.
Related answer: stackoverflow.com/questions/12023359/what-do-the-return-values-of-node-js-process-memoryusage-stand-for/72043884#72043884
First using
topp
from stackoverflow.com/questions/1221555/retrieve-cpu-usage-and-memory-usage-of-a-single-process-on-linux/40576129#40576129 let's observe the memory usage of some baseline cases.A C hello world with an infinite loop at the end has:
- 2.7 MB
- 770 KB
For a Node.js infinite loop nodejs/infinite_loop.js
This gives approximately:
topp infinite_loop.js
- RSS: 20 MB
- VSZ: 230 MB
Adding a single hello world to it as in nodejs/infinite_hello.js and running:
leads to:We understand that Node.js preallocates VSZ wildly. No big deal, but it does mean that VSZ is a useless measure for Node.js.
topp infinite_hello.js
- RSS: 26 MB
- VSZ: 580 MB
Forcing garbage collection as in nodejs/infinite_hello.js brings it down to 20 MB however:
topp node --expose-gc infinite_hello_gc.js
Finally let's see a baseline for
which gives initially:
but after a few seconds randomly jumps to:
so we understand that
process.memoryUsage
nodejs/infinite_memoryusage.js:
node --expose-gc infinite_memoryusage.js
{
rss: 23851008,
heapTotal: 6987776,
heapUsed: 3674696,
external: 285296,
arrayBuffers: 10422
}
{
rss: 26005504,
heapTotal: 9084928,
heapUsed: 3761240,
external: 285296,
arrayBuffers: 10422
}
heapUsed
seems constant at 3.7 MBheapTotal
is a very noisy, as it starts at 7 MB, but randomly jumps to 9 MB at one point without apparent reason
Now let's run our main test program.
First a baseline case with an array of length 1:
This gives the same results as
with:
node --expose-gc bench_mem.js n 1
node --expose-gc infinite_memoryusage.js
. The same result is obtained by doing:
a = undefined
node --expose-gc bench_mem.js dealloc
Not let's vary the size of
which gives:
n
a bit with:
node --expose-gc bench_mem.js n N
N | heapUsed | heapTotal | rss | heapUsed per elem | rss per elem |
---|---|---|---|---|---|
1 M | 14 MB | 48 MB | 56 MB | 10 | 30 |
10 M | 122 MB | 157 MB | 176 MB | 18 | 15 |
100 M | 906 MB | 940 MB | 960 MB | 9 | 9.3 |
"
rss
per elem" is calculated as: rss
- 26 MB, where 26 MB is the baseline RSS seen on n 1
.Similarly "
heapUsed
per elem" deduces the 4 MB (approximation of the above 3.7 MB) seen on n 1
.Note that to reach MAX_SAFE_INTEGER we would need 8 bytes per elem worst case.
Everything below 100 million (8) is therefore very memory wasteful in terms of RSS.
If we use
we see that the memory is now, unsurprisingly, accounted for under
Results for different N:
We see therefore that typed arrays are much closer to what they advertise (4 bytes per element), even for smaller element counts, as expected.
Int32Array
typed array buffers instead of a simple Array
:
node --expose-gc bench_mem.js array-buffer n N
arrayBuffers
, e.g. for N
1 million:
{
rss: 31776768,
heapTotal: 6463488,
heapUsed: 3674520,
external: 4285296,
arrayBuffers: 4010422
}
|| N
|| `arrayBuffers`
|| `rss`
|| `rss` per elem
| 1 M
| 4 MB
| 31 MB
| 5
| 10 M
| 40 MB
| 67 MB
| 4.6
| 100 M
| 40 MB
| 427 MB
| 4
Now let's try one million objects of type
gives:
Disaster! Memory usage is up to 70 MB! Why?? We were expecting only about 24, 4 baseline + 2 * 10 for each million int?!
{ a: 1, b: -1 }
:
node --expose-gc bench_mem.js obj
{
rss: 138969088,
heapTotal: 105246720,
heapUsed: 70103896,
external: 285296,
arrayBuffers: 10422
}
And now an equivalent version using
gives the same result.
class
:
node --expose-gc bench_mem.js class
Let's try Array:
is even worse at 78 MB!! OMG why.
node --expose-gc bench_mem.js arr
{
rss: 164597760,
heapTotal: 129363968,
heapUsed: 78117008,
external: 285296,
arrayBuffers: 10422
}
Let's change the number of fields on the object? First as a sanity check:
produces as expected the smae result as:
so adding properties one by one doesn't change anything from creating the literal all at once. Good.
node --expose-gc bench_mem.js obj 2
node --expose-gc bench_mem.js obj
Now:
gives
node --expose-gc bench_mem.js obj N
heapUsed
:- 1: 70M
- 2: 70M
- 3: 70M
- 4: 70M
- 5: 110M
- 6: 110M
- 7: 110M
- 8: 134M
- 9: 134M
- 10: 134M
- 11: 158M