woensdag 9 februari 2011

why java sucks for game development

In my previous post on GWT, I praised how easy it is to develop a complex web application with a server backend with it. You are also saved from having to write code in javascript, which is obviously a very good thing, as javascript is one of the worst languages ever designed. However, you're still programming in java; and java is a terrible language for game development. Defenders might argue that it's a great language to write clean, modular game code; that its performance has greatly improved over the years; that it's different from writing games in c++, but not necessarily worse. I argue that it is. And here's why.

  1. No stack variables. This is the big one. Every variable (except primitive types) is a pointer, and is allocated on the heap. This is great for large objects, but not so great for smaller ones that are allocated all the time. Allocating an object on the heap has obviously been optimized to a great extent by the java compiler, but it's still orders of magnitude slower than stack variables. And you're also subject to the whims of the uncontrollable garbage collection mechanism of java, which makes it unpredictable.

    This issue arose when I was writing an AI opponent for the Line Wars game. Writing AI for a board game comes down to traversing a HUGE tree, repeating the same relatively simple function millions of times in a recursive manner. Up to this point, I was using a Coordinate class, which just contains x and y members and some functions to manipulate these. This is much cleaner than having separate x and y ints for every coordinate you're working with. However, since I was allocating these objects in the recursive functions, the entire algorithm slowed down incredibly, because I was allocating millions of tiny objects on the heap. Additionally, the garbage collection couldn't keep up, and our-of-memory errors quickly emerged, due to leftover, unused Coordinate objects from previous function calls not being deleted fast enough.

    To fix this, I had to pre-allocate all the relevant Coordinate objects and re-use them. In some places, I just removed the Coordinate and replaced it by two separate int's, resulting in much less readable code. None of these were a good solution, but at least I could move on.

    A second, less obvious consequence of this is that it's impossible to efficiently return two values in a recursive function. In C++, you would just return a struct with the data in it, which would be stored on the stack. Or you could use pass by reference variables to return the data. Since java does not support pass by reference and always allocates on the heap, there is just no way to cleanly and quickly return more than one primitive type value in a function.
  2. Automatic garbage collection. For many applications, such as desktop or corporate tools, automatic garbage collection means less error-prone code, less memory leaks and an all-round faster development time. In game development, it can cause you nightmares. Basically, you never know when the garbage collection is going to happen, so you are at the whims of the virtual machine. You can suggest to the VM that it needs to garbage collect by means of the System.gc() function, but this is just a suggestion, and nothing tells you that the VM will listen. In highly recursive code with no stack variables, not having control over the garbage collection can cause serious troubles, as described above.
  3. No operator overloading. This one is pretty obvious. Ever did some heavy duty matrix calculations with java? Then you surely noted how your code quickly started looking like a terrible unreadable mess of getters, setters and other function calls. Operator overloading is great, because it allows the programmer to map functionality to symbols that logically represent this functionality, in a infix notation (the operator is placed BETWEEN arguments). Function calls use a postfix notation: the function is mentioned first, followed by its arguments. This is counter-intuitive, especially in mathematics, where (mostly) everything is infix. ((A * x) + B) / C is just much cleaner and easier to read than B.add(A.multiply(x)).divide(C).
    Oh, and just for the record: this method of matrix manipulation in java will assign a shitload of temporary, quickly discarded objects on the heap, once again slowing down the entire calculation considerably.

1 opmerking:

  1. Interesting read! All valid points. Actually never thought about the first argument. Really sucks if you think about it. Programming in Javascript is not too bad though (loving Node.js!).