
Memory in JavaScript – Beyond Leaks (2019) - todsacerdoti
https://medium.com/walkme-engineering/memory-in-javascript-beyond-leaks-8c1d697c655c
======
londons_explore
Please don't do this.

Just write the code to describe what you really wanted to do, and if that
performs badly, open a bug with V8 or other JavaScript engines to optimize it.

If there are two identical ways to write the same code, the _engine_ is what
should be deciding what's fastest and running that.

In this case, it probably ought to be pulling the "x" field out of this
structure and doing a memset() on the whole thing.

~~~
ResidentSleeper
I'd be surprised to learn that there's any sensible optimization that the
compiler/runtime can make to ensure optimal data locality. The problem is
difficult enough that the entire Unity engine is being pretty much rewritten
to make use of sequential memory access.

In addition, I really, _really_ wish more JS developers actually cared to
learn about optimization and about how the runtime/hardware actually works.
We've "developed" the web to such a slow, buggy mess that I've given up on the
idea that there's any way to fix it. I hope somebody figures out a way to
start over, preferably with no scripting capability, because apparently giving
people any half-baked scripting language results in them soon developing
nuclear footguns with it.

~~~
0-O-0
Significant part of the web is fast and works just fine. Sure experience on
major news and media sites sucks, but you have to realise that it's caused by
their monetisation model and not a platform itself.

------
Leszek
One thing the author doesn't discuss is that the Boom objects themselves are
also in memory, and also have to be accessed -- and can therefore also be
visited not in memory order, and cause cache misses.

Changing this to a "structure of arrays" approach, with the x value inline in
the array, reduces the runtime by another order of magnitude:

    
    
        const arr_x = new Array(ROWS * COLS).fill(0);
    
        function doSoA() {
            for (let i = 0; i < ROWS; i++) {
                for (let j = 0; j < COLS; j++) {
                    arr_x[i * ROWS + j] = 0;
                }
            }
        }
    
         Started data Local
        Finished data locality test run in  0.6168  seconds
         Started data Non Local
        Finished data locality test run in  2.6442  seconds
         Started data Non Local Sorted
        Finished data locality test run in  0.7923  seconds
         Started data structure-of-arrays
        Finished data locality test run in  0.0627  seconds
    

This is a microbenchmark, so that this with a grain of salt of course.

~~~
winrid
Did the same thing in a game I'm working on, although it's in the backend.

Needed to store millions of routes that had a list of step objects.

Reduced memory usage by an order of magnitude by making the route a list of
numbers, since you get rid of all the 8 byte pointers.

[https://blog.winricklabs.com/(02-17-2020)---efficient-
data-s...](https://blog.winricklabs.com/\(02-17-2020\)---efficient-data-
structures-for-mmo-game-backends-in-java.html)

------
darepublic
I remember having nightmares about leaking javascript memory, and then having
a dream where I resolved the issue. Then I woke up, and thought carefully
about my spaghetti js code and thought never mind.

------
Fr33maan
Gamedev in JS here, findings are very interesting. Memory management is really
a pain with JS, the GC is giving me nightmares. Thanks for the article.

------
hmwhy
I was a bit surprised by this. It could be because of a lack of understanding
of JavaScript, but the biggest reason that surprised is because I was under
the impression that the time it takes to access any element in a JavaScript
Array is constant.

I took the code in that post, played around a little bit (code:
[https://jsfiddle.net/x7sz1fhr](https://jsfiddle.net/x7sz1fhr)), and the
results of a hundred runs performed differently are shown below (NodeJS
12.6.1; Chrome 81.0.4044.122; Firefox Developer Edition 78.09b):

1\. "Local", original — NodeJS __ _: 7.05 ± 13.54 ms; Chrome: 6.13 ± 13.91 ms;
Firefox: 241 ± 6.

2\. "Non-local", original — NodeJS: 52.2 ± 1.4 ms; Chrome: 33.8 ± 1.1 ms;
Firefox: 294 ± 2.

3\. "Non-local (sorted)", original — NodeJS: 10.3 ± 0.9 ms; Chrome: 8.58 ±
0.94 ms; Firefox: 200 ± 1.

4\. "Local", array — NodeJS: 5.05 ± 0.90 ms; Chrome: 4.22 ± 0.87 ms; Firefox:
193 ± 1.

5\. "Non-local", array — NodeJS: 51.9 ± 1.4 ms; Chrome: 34.7 ± 01.6 ms;
Firefox: 252 ± 3.

6\. "Local", object — NodeJS: 6.50 ± 9.84 ms; Chrome: 5.80 ± 10.31 ms;
Firefox: 201 ± 2.

7\. "Non-local", object — NodeJS: 58.1 ± 1.2 ms; Chrome: 42.1 ± 3.3 ms;
Firefox: 247 ± 2.

8\. "Non-local", using sorted indices stored in an array — NodeJS: 57.6 ± 7.8
ms; Chrome: 35.9 ± 6.7 ms; Firefox: 155 ± 2.

_ __These numbers are inflated as an artefact of them being the first thing to
run. Please see the comment of @minitech below.

The first thing to note here is that the results obtained for Firefox are not
a mistake: I have repeated by copy-and-pasting the same code a few times and,
unfortunately, in all cases Firefox performed approximately 4–20 times slower
than both Chrome and NodeJS. That's a bit disappointing because a couple of
years ago I switched back to Firefox for its performance (I did some tests
back then for my use cases before I decided). It is odd that the time
difference is between 4–20 times, so it is possible that I did some non-
standard things by quickly modifying the author's code.

A̶n̶o̶t̶h̶e̶r̶ ̶i̶n̶t̶e̶r̶e̶s̶t̶i̶n̶g̶ ̶p̶o̶i̶n̶t̶ ̶t̶o̶ ̶n̶o̶t̶e̶ ̶h̶e̶r̶e̶
̶i̶s̶ ̶t̶h̶a̶t̶ ̶i̶n̶ ̶t̶h̶e̶ ̶s̶e̶c̶o̶n̶d̶ ̶h̶a̶l̶f̶ ̶o̶f̶ ̶t̶h̶e̶
̶a̶r̶t̶i̶c̶l̶e̶,̶ ̶t̶h̶e̶ ̶a̶u̶t̶h̶o̶r̶ ̶m̶o̶d̶i̶f̶i̶e̶d̶ ̶t̶h̶e̶
̶`̶l̶o̶c̶a̶l̶A̶c̶c̶e̶s̶s̶`̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶t̶o̶ ̶a̶c̶c̶e̶p̶t̶ ̶a̶n̶
̶o̶p̶t̶i̶o̶n̶a̶l̶ ̶a̶r̶r̶a̶y̶.̶ ̶T̶h̶a̶t̶ ̶m̶a̶y̶ ̶a̶c̶t̶u̶a̶l̶l̶y̶ ̶h̶a̶v̶e̶
̶s̶i̶d̶e̶ ̶e̶f̶f̶e̶c̶t̶s̶ ̶t̶h̶a̶t̶ ̶a̶r̶e̶ ̶u̶n̶a̶c̶c̶o̶u̶n̶t̶e̶d̶ ̶f̶o̶r̶,̶
̶w̶h̶i̶c̶h̶ ̶i̶s̶ ̶c̶l̶e̶a̶r̶ ̶u̶p̶o̶n̶ ̶c̶o̶m̶p̶a̶r̶i̶s̶o̶n̶ ̶o̶f̶
̶E̶n̶t̶r̶y̶ ̶1̶ ̶a̶n̶d̶ ̶E̶n̶t̶r̶y̶ ̶3̶ ̶f̶o̶r̶ ̶a̶l̶l̶
̶e̶n̶v̶i̶r̶o̶n̶m̶e̶n̶t̶s̶;̶ ̶a̶n̶d̶ ̶E̶n̶t̶r̶y̶ ̶2̶ ̶a̶n̶d̶ ̶E̶n̶t̶r̶y̶ ̶4̶
̶f̶o̶r̶ ̶o̶n̶l̶y̶ ̶F̶i̶r̶e̶f̶o̶x̶.̶ ̶I̶f̶ ̶w̶e̶ ̶i̶g̶n̶o̶r̶e̶ ̶t̶h̶e̶
̶o̶r̶d̶e̶r̶-̶o̶f̶-̶m̶a̶g̶n̶i̶t̶u̶d̶e̶ ̶d̶i̶f̶f̶e̶r̶e̶n̶c̶e̶ ̶a̶n̶d̶
̶f̶o̶c̶u̶s̶ ̶o̶n̶ ̶t̶h̶e̶ ̶t̶r̶e̶n̶d̶s̶,̶ ̶i̶t̶ ̶a̶p̶p̶e̶a̶r̶s̶ ̶t̶h̶a̶t̶
̶t̶h̶e̶ ̶o̶p̶t̶i̶m̶i̶s̶a̶t̶i̶o̶n̶ ̶u̶s̶e̶d̶ ̶i̶n̶ ̶d̶i̶f̶f̶e̶r̶e̶n̶t̶
̶e̶n̶g̶i̶n̶e̶s̶ ̶s̶e̶e̶n̶ ̶h̶e̶r̶e̶ ̶c̶o̶r̶r̶o̶b̶o̶r̶a̶t̶e̶s̶ ̶w̶h̶a̶t̶
̶@̶l̶o̶n̶d̶o̶n̶s̶_̶e̶x̶p̶l̶o̶r̶e̶ ̶h̶a̶s̶ ̶a̶l̶r̶e̶a̶d̶y̶ ̶p̶o̶i̶n̶t̶e̶d̶
̶o̶u̶t̶.̶

There is also a difference between the times for array and objects across
different environment, with an object generally taking longer. This result
isn't directly related to our primary interest here, but I thought it was
interesting and worth pointing out because of object-related concept in
JavaScript.

Finally, if we use a pre-sorted array of indices to access elements in the
original array containing instances of `Boom`, we see a substantial reduction
in the time for Firefox but not NodeJS and Chrome. This result was a bit
"unexpected" but, again, this comes back to the point @londons_explore made.

Edit: updated code to allow the environment to be changed. Edit 2: Edited
according to the of @minitech's comment.

~~~
nbpname
I have similar results when running the code in the Firefox devtools console,
however, running these benchmarks in a dedicated webpage does not show the
results.

When using a dedicated web page, I see results which are in the same ranges as
NodeJS and Chrome: (not comparable, as running on different hardware)

    
    
       Started data Local (original) foo.html:120:11
      Finished data locality test run in  8.6800 ± 1.3095  ms. foo.html:144:11
       Started data Non-local (original) foo.html:120:11
      Finished data locality test run in  38.9400 ± 1.6195  ms. foo.html:144:11
       Started data Local foo.html:120:11
      Finished data locality test run in  6.6600 ± 1.1390  ms. foo.html:144:11
       Started data Non-local foo.html:120:11
      Finished data locality test run in  38.6400 ± 1.5861  ms. foo.html:144:11
       Started data Non-local (sorted) foo.html:120:11
      Finished data locality test run in  12.1800 ± 1.8334  ms. foo.html:144:11
       Started data Local (object) foo.html:120:11
      Finished data locality test run in  14.4500 ± 1.1492  ms. foo.html:144:11
       Started data Non-local (object) foo.html:120:11
      Finished data locality test run in  49.0000 ± 1.6576  ms. foo.html:144:11
       Started data Non-local (sorted array of indices) foo.html:120:11
      Finished data locality test run in  53.8500 ± 2.9725  ms.
    

I opened a bug against the devtools console of Firefox:
[https://bugzilla.mozilla.org/show_bug.cgi?id=1647276](https://bugzilla.mozilla.org/show_bug.cgi?id=1647276)

~~~
hmwhy
Ah! So that's what it is. :( I did the same thing in as you did and I'm now
getting much fast times.

I feel a bit silly now for not having looked into it further before posting
those obviously suspicious times.

Edit: and thank you for taking care of the bug report!

