Today I am going to talk about optimizing code for the android platform. I have almost finished the game engine, and the last steps of development are focused on performance to ensure that this game runs at 60fps on most devices.
I will focus on Android, and I will orient this article to game development. If you want to learn about optimization in general, there are good resources at Wikipedia, for example. Anyway, pay attention to something: I am doing the optimizations in the last steps. Obviously, the engine design was thinked with good performance in mind, but when it comes to code optimization, this must be the last thing you do for two reasons:
- You can not know for sure that it is worth optimizing a piece of code until you integrate all of them.
- The optimization introduces bugs and obfuscates code.
Returning to android specifics, the first thing that you must learn is how to use the profiler. Traceview must become your best friend before you optimize a single line of code. It will lead you to the 10% of your code that spends the 90% of the time. This is very usefull, as you will be able to keep the remaining 90% of code easy to read and free of extra bugs.
Although the most documented way to profile code is to modify your source code with profiler entry and exit points, there is a very convenient way to use the android profiler directly from the Eclipse ADT without changing your source code. First of all, open the DDMS eclipse perspective:
Now, with the perspective opened, select the app that you want to profile, and start method profiling:
Play a bit with your app, or just wait if it already handles some processing while you wait, and then press the same button again to collect the data and visualize it. After this point, just follow the Traceview documentation to interpret the data.
Now that you already are the master of Traceview, you are ready to optimize those methods that use most of the CPU time. At all times, keep in mind the basic principle of optimization: the fastest code is the one that does not run.
Despite the rapid growth of Froyo, most of the devices in the market use older android systems (at the time of the write of this article, at least). This means that most of your potential users (more than 70% at this moment) will not have JIT in their devices. This will obviously change in the future, but at this moment, you can not ignore the 70% of the market.
The lack of JIT damages performance in lots of ways. The obvious one is that your code runs in a virtual machine, and thus is interpreted, but there is another one, more subtle: the lack of compiler optimization.
In the past, in a galaxy far far away, the Java compiler used to perform optimizations in the code at compilation time. This meant that you could write code like this:
double x = y*sqrt(z*2) + 10; double k = y*sqrt(z*2) + 12;
…and expect the compiler to do the square root and the multiplication just once, reusing the result for both expressions.
Well, this among other optimizations are not made by the compiler anymore, and the reason is the JIT present in every modern desktop Java runtime. The JIT has more chances for optimizing the code because it knows how it runs.
This means that if you need to optimize some method, the first thing that you can do is to perform classical optimizations to your code.
The garbage collector
As you may already know, the garbage collector is a system that automatically frees memory that is not referenced anymore. Android also has one of these, and you can’t predict when it will run, but you can predict how will it impact your performance: badly!
There is no problem with the gc running while the application is not doing realtime tasks, but during the main game loop you should avoid the gc at all costs. It introduces unpredictability and delays that may cause glitches in the gameplay.
There is one simple way of avoiding the GC from running during the main game loop: do not allocate any memory! It is not that simple, and may sometimes dirty code, but it is possible, I have made it in my engine. Allocate all the needed objects before entering the main game loop and then just use them.
Sometimes it is easy to forget about automatic allocations in Java, because it has some syntactic sugar that hides them to the programmer, but here you have a couple of extra tricks:
- Avoid foreach syntax with collections. It allocates a temporary Iterator. If normal arrays do not fit your problem, use ArrayList and use a classic for loop indexing it with an integer.
- Avoid constructing strings by concatenating objects and other strings. This concatenation creates at least one temporary StringFormatter, and sometimes several.
- Any time that you use a third-party method (like from the Java SDK) that returns an object, make sure that this object is not created during the call.
I keep this in a section independent from the JIT because at least with Froyo, this affects also JITted code.
If you code in C++, you are used to automatic method inlining, that makes writing getters and setters, to properly encapsulate code, a pleasure. The fact is that the Java compiler does not automatically inline code, and even with the current JIT implementation, there is not method inlining.
This should be your latest choice when seeking for optimization, as it breaks encapsulation and opens the can of worms, but it is sometimes needed to avoid method calls and make some attributes public. A good example of this is a Vector class for storing coordinates, or the android Rect class.
Set a goal
It is easy to get lost in optimizationland. Before any optimization, be sure to set a goal, and when you get to it, stop optimizing. Seriously!
Be careful to optimize the code, try to avoid it whenever possible, and remember that all code that you optimize, you will have to maintain it sooner or later. Keep excelent documentation on every optimized code!
You can learn more about android optimization in Designing for performance, from the android development guide.