JeromeDane/HearthStats.net-Uploader — pull request #141 by gtch (2014-02-19 13:33:43+00:00)
Title: #139 Performance and memory use improvements

Here's a change to when JVM garbage collection occurs which makes a big improvement to the performance and memory use on OS X. I expect it will help in Windows too, but I highly recommend that you test in Windows to be sure it's not having any negative side-effects there before you release.

Left unchecked on OS X the memory usage would slowly creep higher and higher up to several gigabytes. This is a graph of the typical memory use playing a single game in Hearthstone without any instructions to the JVM:
![jvm heap - automatic gc](https://f.cloud.github.com/assets/1667484/2206713/66bfc074-9967-11e3-95ae-014320a51921.png)

The jagged lines indicate a very large amount of data accumulating in memory before eventually the garbage collector comes along and clears it after a few seconds. Analysing this in VisualVM suggests that this memory use is almost entirely the raw image data capture from screenshots, because that's the only use of int[] arrays:

![visualvm memory](https://f.cloud.github.com/assets/1667484/2206727/dc9a9c60-9967-11e3-9b7e-46f6524353c6.png)

So in short all those screenshots pile up in memory then after several seconds they get cleared out.

When I reduced the amount of memory available by specifying an -Xmx property on the JVM then it would GC more often but I started to see what looked like suspicious pauses in my debug logs; I had added timing logs to my fork to see which code needs to be optimised, and those logs started to have gaps of several seconds where nothing occurred. This suggests that the JVM was doing some big stop-the-world GC sweeps that put everything on hold. During those seconds no detection of the Hearthstone screen occurred, so that could be a cause of events being missed occasionally as reported by #139. Experimenting with different memory settings it seems to me that the more constrained the memory, the more likely it is that long pauses occur.

I tried forcing the JVM to perform a GC after every poll, ie. in the 50ms break between polls. This turned out to be very effective at reducing memory usage. This graph of memory usage of single game shows just how little memory is used:
![jvm heap - gc every cycle](https://f.cloud.github.com/assets/1667484/2206791/05af6f3a-9969-11e3-8323-6f526e076c75.png)

However this puts way too much load on the CPU. I found it taking on average 20-21% CPU time on my 2009 Core i7 iMac. Worse still, there were some devastatingly long pauses in the JVM where nothing happened — the worst being nearly 15 seconds long:
![jvm cpu - gc every cycle](https://f.cloud.github.com/assets/1667484/2206805/5e6d9e12-9969-11e3-9ded-215ecca62ec5.png)

After trying a few runs, I found a reasonable balance is to force the JVM to do the GC after every fifth poll. This results in fairly low memory usage with acceptable CPU use (around 7-8% CPU for me) and few pauses (though still some!):

![jvm heap - gc every fifth cycle](https://f.cloud.github.com/assets/1667484/2206818/c8c887cc-9969-11e3-977c-ec39e2c7d387.png)

So this code change adds a forced GC after every fifth cycle. It also limits the memory use of the OS X bundle to 192MB (I think less than that makes the risk of long pauses too high).

I found that GC tends to take 40ms in total, which is OK on my machine but I'm concerned that slowed Macs will still be performing a GC when the next cycle comes around. So I've also increased the default polling interval to 80ms instead of 50ms. I'm not finding anything gets missed with 80ms, I've played several games with full data captured AFAIK.

This change certainly helps OS X, but please test this on Windows to ensure that I'm not making things worse there.