“Dang, my game is running really sluggish. I know, I’ll just optimize it! (*opens code editor*) …ok, how do I actually do that?”
First things first: You can’t fix something until you know what to fix. You’ll probably know a few symptoms: Maybe the game freezes up every once in a while, or the game crashes on old phones because it runs out of memory, or maybe the framerate just seems to get choppy every now and then. You might even have a guess as to what’s causing the problem, but before you dive into your code to start fixing everything, make sure you’re fixing the right thing.
If you can, an easy first step is to run a profiler. Essentially, a profiler tracks how long different parts of your program take to execute, letting you drill down deep into your code to figure out how long each function call takes. While off-the-shelf tools are generally the easiest option, sometimes they’re not available or are impractical. You can still do the same thing manually, inserting code to check the current time before and after a function is done running and dumping that info out to a log file.
So, what data are we looking for? Anything that’s taking too long, of course! But what counts as “taking too long”?
To answer that, let’s talk about frames per second (FPS), also known as frame rate. This is a measurement of how many images are presented to the player in one second. From a programmer’s perspective, this measures how many times you can loop through your game code in one second. Generally speaking, your goal is to never dip below 30 FPS. If your game requires a lot of really responsive controls, like a first-person shooter game, you might even want to push for 60 FPS. Having a target FPS is important, but FPS itself isn’t a good measure of our game’s performance when optimizing because it’s non-linear. Going into the details behind that is a bit of a tangent, but you can read more about it here: http://www.mvps.org/directx/articles/fps_versus_frame_time.htm
Instead of frames rendered per second, what we really want to measure is: how long does each frame of our game take? Instead of (frames / second), what we want is (seconds / frame). A second is a really long time in terms of computation, though, so we typically use milliseconds. To convert from (frames/sec) to (sec/frame), we just take the inverse.
If our goal is 30 FPS, we have (1 / 30) = 0.03333… sec = 33.33… milliseconds per frame
If our goal is 60 FPS, we have (1 / 60) = 0.01666… sec = 16.66… milliseconds per frame
Finally, we’ve reached a reference point! Assuming our target is 30 FPS, if one iteration through our game’s code takes longer than 33.3ms, it is officially “taking too long”. Huzzah!
Armed with this knowledge, what do we look for in the profiler data?
If the game briefly “freezes” every now and then, look for spikes in performance time:
Not all performance hitches will be this visually obvious, but it’s the same idea: look for a sudden jump up in frame time. The frame pointed to by the arrow took a lot longer to process than the others. If we look at the color-coding, we can deduce that something in our physics setup caused the problem. Click on that frame in the window, then look at the list of function calls to see what happened that frame. Try and find a way to consistently reproduce the problem and get as much data as you can. Maybe we just created a bunch of objects? Maybe we moved around a bunch of “static” colliders that don’t have rigidbodies and forced the engine to re-calculate all the physics data?
If the performance issue comes from your scripts, look for what function calls took the most time. Dig as far down as possible to make sure you’re fixing the right problem. I recently spent a bunch of time “fixing” a performance spike that happened whenever we would load in new level data in an endless runner. “Of course, adding all of those objects at once is causing it, duh!” I said to myself. After a few hours changing the code to slowly stream in the new objects a handful at a time… the problem was still there. Oops. Turns out the performance hitches were actually caused by loading in animation data for objects we hadn’t used yet. Fixing that took all of twenty minutes to make the game pre-load animations beforehand; I didn’t need to re-work our level loading system at all. I could’ve avoided wasting that time if I had just spent a few more minutes digging deeper into the profiler data.
If your game is consistently slow and sluggish rather than just having “spikes” like the above, you’ll need to work a bit harder. If you’re really, really lucky, it’s one subsystem or function taking way too long and you can just fix it and move on with your day. More often than not, though, your update loop is just taking too long overall across… well, everything. Rather than your performance being killed by one big orcish axe to the chest, it is instead being subjected to death by ten thousand paper cuts.
The main question you now have to answer is, how much time does each subsystem get? Remember, you only get 33.3ms to spread across every single system in your game at 30 FPS. In a big AAA project, there will probably already be a performance budget given to each subsystem: “OK, AI gets [X] ms, physics gets [Y] ms…”. For a smaller team, you probably don’t have this level of formality yet. Now you have to change that. Even after you get your performance back to a manageable level, every single thing you add will potentially push it right back over, so it’s important to keep things in check. Generally, you’ll want to start with whatever subsystem takes the largest amount of time, and work your way down from there.
Once we figure out the source of our performance issues, how do we fix it? Well, that’s a big, complex problem that I’ll write about more later. In the meantime, hopefully this post has helped you figure out where to get started!
(For the next article, see Optimization, Part 2: When do you pay the cost?)