Okay, rant mode on. As I was waiting for Eclipse to launch again today, it occured to me that one of the enduring mysteries of Java (and C#/.NET) for me is the continued dominance of just-in-time compilation as a runtime strategy for these languages, wherever they’re found. We’ve all read the articles that claim that Java is “nearly as fast as C++”, we also all know that that’s a bunch of hooey, particularly with regard to startup time. Of course, if Eclipse wasn’t periodically crashing on me with out-of-memory errors, then I’d care less about the startup time – but that’s another rant. Back to startup time and JIT compilation…
If you’re creating a server-based application, the overhead of the JIT compiler is probably pretty nominal – the first time through the code, it’s a little pokey, but after that, it’s plenty fast, and you’re likely throttled by network I/O or database performance, anyway. And in theory, the JIT compiler can make code that’s optimal for your particular hardware, though in practice, device-specific optimizations are pretty minimal.
On the other hand, if you’re writing a desktop application (or worse yet, a piece of embedded firmware), then startup time, and first-time through performance, matters. In many cases, it matters rather a lot.
There are a number of advantages to writing code in a “managed”, garbage-collected language, especially for desktop applications – memory leaks are much reduced, you eliminate buffer overflows, and there is the whole Java standard library full of useful code that you don’t have to write for yourself. I’m willing to put up with many of the disadvantages of Java to gain the productivity and safety advantages. But waiting for the Java interpreter to recompile the same application over and over offends me on some basic level.
On a recent project, we used every trick in the book to speed up our startup time, including a “faked” startup splash screen, lazy initialization of everything we could get away with, etc, etc. Despite all that effort (and unecessary complication in the code base), startup time was still one of the most common complaints from users.
Quite a bit of profiling was done, and in our case, much of the startup time was taken up deep inside the JIT, where there was little we could do about it. Why oh why doesn’t anybody make a Java (or .NET) implementation that keeps the safe runtime behavior, and implements a simple all-at-once compilation to high-performance native code? Maybe somebody does, but I haven’t heard of them.
For that matter, why don’t the reference implementations of these language runtimes just save all that carefully-compiled native code so they can skip all that effort the next time? The .NET framework even has a global cache for shared assemblies. Why those, at least, aren’t pre-compiled during installation, I can’t even imagine.
I was helpfully reminded of NGen, which will pre-compile .NET assemblies to native code. I had forgotten about that, since so much of my most recent C# work was hosted on Mono, which does things a bit differently. Mono has an option for AOT (ahead of time) compilation, which works, sort of, but could easily be the subject of another long article.