Improved Memory Usage in Poker Copilot
Warning: This is a highly technical post. If phrases like “garbage collection algorithm” and “hash map” make you bored already, don’t read on.
The unofficial 2.17 version of Poker Copilot fixed all major known issues, except one. Sometimes Poker Copilot crashes complaining that it has run of memory. The exact error message is “java.lang.OutOfMemoryError: Java heap space”. In Java parlance, this is the feared OOME problem.
There are two ways to deal with a Java “out of memory” error:
- You can increase the amount of memory available to the Java program, or
- you can track down what part of your program is hogging memory.
The first approach is the easiest. But it creates other problems, such as excess memory paging on computers with modest amounts of RAM. It’s also avoiding the real issue.
The second approach takes discipline, and I’ve avoided it for some time. But finally these last few days I got to it. With the help of JVisualVM and loyal Poker Copilot customer Andy’s 100 Megabyte hand history file, I discovered a couple of problems with the Poker Copilot code.
First, I had an almost pointless hash map in memory that contained 900,000 items. Was it coincidence that I had 900,000 hands in my hand history database? No. Problem identified. Problem eliminated.
The second memory gorging problem was caused by Poker Copilot’s parser. This is vaguely how parsing works in Poker Copilot:
Step 1: Obtain the chronological latest hand history file that needs parsing
Step 2: Turn the hand history file into a list of hands
Step 3: Insert the hands in that list into the database one by one
Step 4: Lather and repeat
This works fine with lots of small hand history files. But add a big hand history file, say Andy’s 100 Megabyte monster, and things grind to a halt. Step 2 results in a list containing thousands, tens of thousands of hands, eating precious memory.
I changed the parsing as follows:
Step 1: Obtain the next hand history file that needs parsing
Step 2: Parse a hand from the hand history file
Step 3: Insert the hand into the database
Step 4: If the hand history file still has more data, repeat from step 2
Step 5: There is no step 5
Step 6: Repeat from step 1
This is a gross simplification. There’s multi-threading and queues in there to keep things humming along at a brisk pace.
The result? The nasty “java.lang.OutOfMemoryError: Java heap space” problems are gone. Poker Copilot runs well now in its allocated 256 Megabytes.
The unexpected additional result? Poker Copilot is faster. The thread that parses hand history files and the thread that inserts hands into the database co-operate on a “just in time” basis that would make Toyota proud.
Something even better? Better use of memory leads to better garbage collection performance in Java, speeding up the overall Poker Copilot show. The engineers at Sun who build the Java Virtual Machine’s garbage collector noticed something a few years back. Most Java objects are short-lived. So they optimised for short-lived objects, using an algorithm called “Generational garbage collection”. A result of this is that if a high proportion of your Java objects have a short life (that is, mere milliseconds), the garbage collection works more efficiently. The new Poker Copilot approach to parsing the hand history files has only short-lived objects, and therefore works even faster.
How fast? My 2009 iMac gets through a million hands in two hours.
I find it enormously satisfying to fix a problem such as this.
These improvements will be included in the next update.