The problem is though, that despite all the advances with Just In Time compilers (JITs) and other speed improvements, Java is still slower than native languages. All is not lost though, there are a few things, which if you remember to do while coding, can increase the execution of your Java code by up to 250% in places! Games need to be fast, so here's some ways of squeezing that last caffeinated drop of performance from Java...
Rules of Optimization:
1. Don't do it.
2.(For experts only) Don't do it yet"
- M.A. Jackson
The three main things which affect performance in Java are:
[size="5"]API Selection
The Application Programming Interface (API) which comes with Java is a standard library of classes across all platforms, the only differences are between the different versions of Java, 1.0,1.16,1.18,1.2,1.22,1.3 etc, and the different editions, Java Standard Edition, Java Micro-Edition Java 2 Enterprise Edition etc. You don't need to worry about that for now though. Basically the rule here is that with a lot of the classes in the api they make use of JNI calls to faster native implementations. For example, rather than use the following piece of code to copy an array:
for(int i = 0; i < source.length; i++)
destination=source;
System.arraycopy(source,0,destination,0,source.length);
aVector.addElement(new Integer(anInt));
anInt = (Integer)aVector.elementAt(index).intValue();
[size="5"]The Compiler
Most people who have used languages such as C/C++ are used to reams of options to optimize their code by doing things such as targeting different processors, different memory models etc. etc. In Java we are lucky to have even the one option we do, and most people don't even know it exists! The option I'm talking about is the -O option which turns inlining on.
Inlining is when chunks of code are copied across from one part of the source to another so as to speed up execution. With C/C++ all you have to do is prefix the function with the inline keyword and the compiler does it for you. In Java you tell it to consider inlining functions and variables when you compile with the -O option. For a method/variable to be considered for inlining it must meet the following criteria:
- It must be declared as either private, final or static.
- It can't be synchronized.
- Inlined methods can't contain local variables. Once javac (the compiler) has this list of candidates, it goes through them and makes the following decisions:
- If it's a variable, it will usually treat it like a #define in C/C++ and place the value directly in the code, rather than a reference.
- If it's a method it will decide whether it thinks it is a good idea to inline or not. In most cases it only inlines short methods, about 2 or 3 lines long. There's a couple of tricks you can use with this:
[size="3"]Trick #1
If there are a few methods in your program which are proving to be bottlenecks and they can't be inlined, a possible solution is to make a subclass of the class they are contained in and then making them final or private. Bear in mind that this is very bad practice and by no means an ideal solution.
[size="3"]Trick #2
Static final constants get stored in a class file as well as being inlined. Try putting them in an interface and then implementing that interface in each class which uses them, that way they only get stored once and the calls to them are still fast.
[size="3"]Static and Dynamic binding
Another thing which the -O option affects is static/dynamic binding. When some code calls a method it either dynamically or statically 'looks it up'. With static binding the code knows exactly where the method is going to be and directly calls it. With dynamic binding the code does not know where the method is and has to look for it at runtime. All of this is transparent to the coder. It's the Java Virtual Machine (JVM) and compiler which has to worry about this. Because of Java's architecture, most things are dynamically bound, that means that only the class you modify has to be recompiled. When you use the -O option, it allows static binding for methods and fields which are only used within a class, and cannot be used outside it. Static binding is much faster than dynamic binding.
[size="5"]The JIT
The JIT reads ahead through each class and compiles it into native code before it is executed. This results in improved execution time, but slower object creation. With most applications, the JIT speeds things up, but with some, where lots of Objects are being created for example, it can actually slow things down. Some JITs have a lot of options which can alter the tradeoff, but many don't. If you want to turn off the JIT, it can be done with the following option:
java -J-Djava.compiler=none MyClass
[size="5"]Some Miscellaneous Speed Demons
[size="3"]Strings
Strings are horrendously slow, take the following example:
At first glance it doesn't look too bad on surface, but in reality it is one of the worst things for slowing things down when you do a lot of String operations, here's what happens. System.arraycopy() is used to copy y and z into a temporary StringBuffer and then toString() is called on the temporary StringBuffer. Try using StringBuffer directly when performing operations such as these.for(int i = 0; i < source.length; i++)
String x;
String y = "Oh";
String z = " No!";
x = y + z;
[size="3"]I/O
Always wrap a BufferedReader around an InputStream, and a BufferedWriter around an OutputStream. Reading one byte at a time from a stream is just too slow.
[size="3"]Graphics
Override the repaint() method and use clipping when implementing custom graphics. Use double buffering.
[size="3"] Static Initializers
Make use of them, for those that don't know, they are a block of code contained in curly braces which gets called the first time an object is initialized. They are very handy for all sorts of things, and can be use to do constructor work which only needs to be done once for all instances of a class.
[size="3"]Profiling
Profile your classes to determine the bottlenecks. A profiler which comes with java is the -prof option. Run your class using java -prof myclass. Java will dump information about the execution to the local directory.
[size="3"]Make use of lessons learned with other languages
A lot of optimizing techniques apply to all languages, use them. For example read through the article: Performance Programming in C++, by Jorris Timmermans, it has a lot of content which can be applied to Java among others. Most important are the use of bit shifts and loop optimization.
[size="3"]Being careful about which primitives you use
As a rule of thumb, ints are faster than longs and floats are faster than doubles, use them in preference wherever possible.
[size="3"]Make use of threads
This isn't an optimization thing, but make use of threads when responding to GUI actions, rather than locking the application, the GUI will appear to be much more responsive, than if all the processing was done before the handler returned.
[size="5"]Final Note
These are basically all a collection of ideas I have come across in my year as a Java Programmer so far, I'll probably come across many more in the future. None of the things above are laws, they aren't necessarily the fastest ways.
As well as in books which I don't remember the title of ;-) further information can be found on byte codes and the like at: http://www.cs.smu.edu/~jch/java