title: Understanding JavaScript heap growth and GC pattern
body: <p>In a vain attempt to write perfect javascript I am tackling the issue of the Javascript heap. I have got it down to the lowest level I can but I have run out of options, and don't understand what is going on (well my guess is rAF overhead, but guesses don't count).</p>
  <p><strong>The heap's sawtooth pattern (in light blue):</strong></p>
  <p><a href="https://i.stack.imgur.com/4zxkO.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/4zxkO.jpg" alt="enter image description here" /></a></p>
  <p>The above timeline is from a simple full page canvas particle render. The aim of the exercise is to reduce the amplitude of the heap's sawtooth, and hopefully also increase the period between cleanups.</p>
  <p>Looking closer, the heap is growing about 15k every 60th of a second, and falling from 3.3MB to 2.4MB every ~1 second</p>
  <p><a href="https://i.stack.imgur.com/GTtSV.jpg" rel="noreferrer"><img src="https://i.stack.imgur.com/GTtSV.jpg" alt="enter image description here" /></a></p>
  <p>What I do not understand is the timing and the growth amount 15K.</p>
  <p>The heap grows by 15kb just before execution idle, and ~0.015ms after the following function has return to idle (below is my top level function).</p>
  <pre><code>var renderList = [];    
  var stop = false;
  var i;
  
  function update(timer) { // Main update loop
      if(!stop){
          requestAnimationFrame(update);
      }
      for(i = 0; i &lt; renderList.length; i ++){
          renderList[i](timer,ctx, w, h);            
      }
  }
  </code></pre>
  <p>Nothing I do to the code is reducing or changing the location of the heap growth. The allocation profile shows that am not allocating any memory. GC is at 0.08% on the CPU profile (what it is doing I don't know?, does it also manage the heap?)</p>
  <p>Can someone please explain to me what this memory is being used for? and how I can reduce it or make the line flat?</p>
  <p>I understand that there may be nothing I can do, but at the moment I dont have the vaguest idea what is being put on the heap? It would be nice to know.</p>
  <p>The snippet is just the code called from <code>update</code> (code snippet above) I don't think it is relevant, but just in case. It is the code that has executed and returned just before the heap grows.
  <div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>        var p,d,s;
          var renderCount = 0;
          var fxId = 0;
          var lastTime;
          var frameTime = 0;
          var minParticles = 10;
          var particleCount = minParticles;
          var frameSum = 0;
          var frameAve = 0;
          var frameWorkTime = 0;
          var lastFrameWorkTime = 0;
          var particleRenderTimeMax = 0;
          var m = 0;
          var mC = 0;
          var mR = 0;
          var then,tx,ty;
          var renderTime,then1; 
  
          //=====================================================================================
          // the following function is out of context and just placed here as reference
          /*
          draw : function (image, index, x, y, scale, rotation, alpha) {
              spr = image.sprites[index];
              ctx.setTransform(scale, 0, 0, scale, x, y);
              ctx.rotate(rotation);
              ctx.globalAlpha = alpha;
              sh = spr.h;
              sw = spr.w;
              if(spr.vx !== undefined){  // virtual sprite dimensions
                  _x = -spr.vw / 2 + spr.vx;
                  _y = -spr.vh / 2 + spr.vy;
                  ctx.drawImage(image, spr.x, spr.y, sw, sh, _x, _y, sw, sh);
                  return;
              }
              ctx.drawImage(image, spr.x, spr.y, sw, sh, -sw / 2, -sh / 2, sw, sh);
          },
          */        
          //=====================================================================================        
          
          // Add particle
          function addP(x,y,spr){
              p = particles.fNextFree();
              if(particles.fLength &gt;= particleCount || p === undefined){ // no room for more
                  return;
              }
              p.x = x;
              p.y = y;
              p.spr = spr;
              p.life = 100;
              p.s = Math.random() +0.1
              d = Math.random() * Math.PI * 2;
              s = Math.random() * Math.PI * 2;
              p.dx = Math.cos(d) * s;
              p.dy = Math.sin(d) * s;
              p.dr = Math.random()-0.5;
              p.maxLife = p.life = 100-spr*10;
          }
          // move and draw particle
          function updateDrawP(p,i){
              if(i &gt;= particleCount){
                  p.life = undefined;
                  return;
              }
              s =  p.life/p.maxLife;
              p.x += p.dx * s;
              p.y += p.dy * s;
              p.r += p.dr;
              p.life -= 1;
              
              if(p.life === 0){
                  p.life = undefined;
                  return;
              }
              renderCount += 1;
              sDraw(spriteSheet, p.spr, p.x, p.y, p.s, p.r, s); // Calls draw (function example above)
          }
        
          
          function renderAll(time) { // this is called from a requestAnimationFrame controlled function
              var then = performance.now(); // get frame start time
              var tx, ty;
              if (lastTime !== undefined) {
                  frameTime = time - lastTime;
                  frameSum *= 0.5;
                  frameSum += frameTime;
                  frameAve = frameSum * 0.5; // a running mean render time
              }
              lastTime = time;
              ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
              ctx.globalAlpha = 1; // reset alpha
              ctx.clearRect(0, 0, w, h);
              if (spriteSheet.sprites) { 
                  mouseWorld = EZSprites.world.screen2World(mouse.x, mouse.y, mouseWorld);
                  if (mouse.buttonRaw &amp; 1) {
                      fxId += 1;
                      fxId %= EZSprites.FX.namedFX.length;
                      mouse.buttonRaw = 0;
                  }
                  if (mouse.buttonRaw &amp; 4) {
                      world.posX += mouse.x - mouse.lastX;
                      world.posY += mouse.y - mouse.lastY;
                      EZSprites.world.setPosition(world.posX, world.posY);
                      mouseWorld = EZSprites.world.screen2World(mouse.x, mouse.y, mouseWorld);
                  }
                  if (mouse.w !== 0) {
                      if (mouse.w &gt; 0) {
                          EZSprites.world.zoom2Screen(mouse.x, mouse.y, ZOOM_AMOUNT, true);
                          mouse.w -= ZOOM_WHEEL_STEPS;
                      } else {
                          EZSprites.world.zoom2Screen(mouse.x, mouse.y, ZOOM_AMOUNT, false);
                          mouse.w += ZOOM_WHEEL_STEPS
                      }
                      mouseWorld = EZSprites.world.screen2World(mouse.x, mouse.y, mouseWorld);
                      EZSprites.world.getWorld(currentWorld);
                      world.posX = currentWorld.x;
                      world.posY = currentWorld.y;
                  }
  
                  // sets the current composite operation (only using source-over)
                  EZSprites.FX[EZSprites.FX.namedFX[fxId]]();
  
                  // render and time particles
                  renderCount = 0;
                  var then1 = performance.now();
                  
                  particles.fEach(updateDrawP); // render all particles
                  
                  var renderTime = performance.now() - then1;
  
                  EZSprites.context.setDefaults();
  
                  // gets the total time spent inside this function
                  frameWorkTime += performance.now() - then;
                  lastFrameWorkTime = frameWorkTime;
                  if (renderCount &gt; 0) {
                      particleRenderTimeMax = Math.max(particleRenderTimeMax, renderTime / renderCount);
                      particleRenderTimeMax *= 10;
                      particleRenderTimeMax += renderTime / renderCount
                      particleRenderTimeMax /= 11;
                      // Smooth out per particle render time max
                      m = particleRenderTimeMax;
                      mC += (m - mR) * 0.1;
                      mC *= 0.1;
                      mR += mC;
                      // Particle count is being balanced to keep ensure there is plenty of idle time before
                      // the next frame. Mean time spent in this function is about 8 to 9ms
                      particleCount = Math.floor(((1000 / 120) - (frameWorkTime - renderTime)) / (mR));
                  }
                  // This is where frameWorkTime begins its timing of the function
                  then = performance.now();
                  frameWorkTime = 0;
  
                  if (particleCount &lt;= maxParticles) {
                      particles.fMaxLength = particleCount;
                  }
                  // Add particles. 
                  addP(mouse.x, mouse.y, 1);
                  addP(mouse.x, mouse.y, 2);
                  addP(mouse.x, mouse.y, 3);
                  addP(mouse.x, mouse.y, 4);
                  addP(mouse.x, mouse.y, 5);
                  addP(mouse.x, mouse.y, 1);
                  addP(mouse.x, mouse.y, 2);
                  addP(mouse.x, mouse.y, 3);
                  addP(mouse.x, mouse.y, 4);
                  addP(mouse.x, mouse.y, 5);
              }
              mouse.lastX = mouse.x;
              mouse.lastY = mouse.y;
              frameWorkTime = performance.now() - then;
          }</code></pre>
  </div>
  </div>
  </p>
  <hr />
  <h2>Update snippet</h2>
  <p>As asked in comments below is reproducible HTML doc.</p>
  <p><strong>Note</strong> this example can not be hosted in sites like CodePen or StackOverflow as they modify monitor and or execute source of addition code that interferes with the test</p>
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
      &lt;head&gt;&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html;charset=ISO-8859-8&quot;&gt;&lt;/head&gt;
      &lt;body&gt;&lt;script&gt; 
      &quot;use strict&quot;;
      (() =&gt; {
          var renderList = [], stop = false, i, ctx;
          requestAnimationFrame(function update(timer) { // Main loop
              if (!stop) { requestAnimationFrame(update) }
              for (i = 0; i &lt; renderList.length; i ++){
                  renderList[i](timer, ctx, w, h);            
              }
          });
      })();
      &lt;/script&gt;&lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  <p>Running the above example has the heap grow over 60secs before a major GC is called with the heap growing approx ~300bytes per frame.</p>
  <hr />