Today was spent in Heap Profiler

After implementing levels with hardest difficulty that have hordes of enemy waves, I hit performance problems. The browser would become visibly unresponsive near the end of the level. So today I spent debugging those problems. I profiled the entire level 1 run in the Chrome dev tools memory profiler (as can be seen in the screenshot) at every step. At the end of the day, I've managed to get level 1 running without any hiccups at 60fps until the very end. Here are some things I found and fixed. Might be helpful to other HTML5 game developers.

  • It was clear that the performance problem was proportional to number of spawned enemy units (I call them ghosts in the code). Since I create a Javascript object for each of them, I thought it was too heavy. The heaviest part of it was the Spine object (the JS library from Spine animation tool). It was no longer necessary to use spine objects for each ghost, because they don't have any significant animation. So I removed them and replaced them by simple pixi container. Unfortunately, this didn't help the problem a bit.
  • To further investigate I started disabling various parts of the code. At times replaced ghost objects, by only a single pixi sprite object. That didn't have any performance impact, so the problem wasn't with rendering or Pixi.js. But during one of these trials, I found that disabling the sound was saving the memory quite a bit. I'm using Howler.js for the audio, but I'll come back to this later. After disabling audio, I could play the whole level, but still it was quite slow near the end.
  • Another thing that was occupying memory in each ghost object was certain array that keeps the path of the ghost. All ghosts share some common paths, therefore they keep a copy of the common path with them. But my ghost movement algorithm was modifying each path (unncessarily in retrospect), therefore I had kept a deep copy of the path in each ghost. After some reflection, I could easily modify the code to not modify the path, thus refer to the single master copy of the path without duplication. This change did have positive effect. The entries in the memory profile tree, that were attributed to the path code, were gone now.
  • After these changes when I ran the profiler again, it ran better all the way to the end. It was at this point that I found an unexpected entry at the top of heap profile tree. It was in the analyse() method of my collision detection system. My collision detection system is pretty primitive and makeshift. I go over all ghosts and all towers in every game tick and then compare if they are within each other's range. The performance problem wasn't due to this computation, but due to the data structure in which I was saving this in-range relations between various towers and ghosts. I was naively using a hashmap. If ghost A is within range of tower B, then there will be a key in the hashmap for "gAtB"! Natually, when the hordes of ghosts were spawned, a lot of memory was being allocated to the strings that were used as keys in this map. When I first coded this one, I knew it will have to be replaced, but had forgotten about it. Today was the day. Since the visibility information is 1-bit information, there was significant scope for saving memory here. I put together a 2D grid of bits implemented on top of Uint8Array, rewired the ID system of ghosts and towers and came up with pretty lean mechanism to record the visibility data.
  • The collision system change saved a bunch. Now was the time to take care of audio. I don't know what Howler.js is doing or if I'm using it wrong, but after a bit of reading I figured I don't need a library to use HTML5 audio. Even this simple tutorial is enough to implement what my needs are. Took me 10 minutes, to replace howler code. I would like to spend more time on this, but this audio code doesn't seem to have adverse effect on memory usage the way Howler had.

Very glad to have fixed this. I'll deploy the new build tomorrow after some more testing.

Leave a comment

Log in with your account to leave a comment.