Ahh! My Groovy Script is Slow

Attack of the Gelatinous Blob uses Groovy scripts for all entities, level configuration, and pretty much most game features. It provides an easy and forgiving way to implement features and reuse them. I’ve also built the game so you can change a script for an entity while the game is running and see the changes in the entity immediately. This has been hugely beneficial.

But that is not what this post is about. This post is about performance when it comes to Groovy scripts and in particular, when loading and compiling them.

I started the investigation into script loading times when I noticed the levels for AotGB were taking longer to load than expected. Especially when many of the same entity were loaded, the time would grow linearly when it shouldn’t have since jMonkeyEngine’s asset manager caches models: once you load it, a clone is returned very quickly. The time to load a level with roughly 400 objects and 600k triangles, texture maps no larger than 1024×1024 took around 17 seconds. This was much too slow to be tolerable.

I had a suspicion that the slowdown was in the Groovy scripts. But just to make sure (as any good programmer should do) I ran the profiler to pin down the time-sink. Sure enough, it was the scripts.

To load the scripts I was doing a little caching: the script files themselves were loaded and cached off of the disk so they could be retrieved from memory quickly. However the compiled script was not being cached. The profiler pointed right to this spot: compile times. These were quite slow and were being repeated for the same script over and over again. That had to change.

I had see the Groovy Script Engine before: an embedded script manager that comes bundled with Groovy. It handles caching for you and checks if a file has changed, and will re-compile the script for you if it has. Great! This exactly what I want. So I slotted it in and ran some loading tests.

Loading times went from 17 seconds to 40 seconds! What happened there? I double-checked if it was running and configured correctly, it was. Some google searching quickly revealed the problem with the Groovy Script Engine, as can be found in this (http://groovy.329449.n5.nabble.com/GroovyScriptEngine-maximizing-performance-td339751.html) discussion. The issue is that it tries to make sure all dependencies are always up to date and that the files haven’t changed.

The script file checking is nice, but I do not really need it since it is only used during development and I enter the script through the groovy console whenever I change it. So that feature can go. All I really want is the pre-compiled script.

The solution I took for this was to just cache it myself. A simple HashMap of the script name and the compiled class.

private Map<String,Script> scriptCache = new HashMap<String,Script>();
...
Script script = getScript(codeKey.getName());
if (script == null) {
    Class<Script> clazz = (Class<Script>) parseClass();
    script = clazz.newInstance();
    cacheScript(codeKey.getName(), script);
}

script.setBinding(binding);
return script.run();

I implemented this and tested it. Load times went from 17 seconds for no caching and 40 seconds using Groovy Script Engine down to 7 seconds! Okay that is much better. The load times are reasonable now.

groovyPerf-chart

There are some things you can do to speed up the Groovy Script Engine, however my solution only took a couple minutes to implement and test, and it will be extremely easy to maintain. So I think I will be sticking with it.

 


			

Leave a Reply

Your email address will not be published. Required fields are marked *


four × 7 =