Why is C faster than Java: git vs JGit
marc.info
marc.info
This is an insightful post from the git mailing list which shows some of the real limitations that a top tier developer hits when trying to write Java code as fast as neatly optimized C code. Definitely worth reading.
Edit: I mean most of the problems (maybe features) of java are known.
It's truly liberating to get back to C after having programmed in something higher level for a long time. It actually makes me appreciate C more. I mean, at first, I shoot myself in the foot, elbow and groin with alarming regularity, but it's nice to actually have access to the bullets. :) [EDIT: dodgy grammar]
EDIT: but as wcoenen points out, this was written in 2009 and Java 1.7 does a better job with some of this.
Yes. I like how you qualify the statement. Furthermore, on the C side you have Linus and other C gurus that really, really know how to exploit the strengths of the C language.
(1) Blind faith in Generics.
This alone screams newb to me. He says that he got better performance with a custom data structure (no shit sherlock) but then seems deeply surprised by this. Duh. Okay, well, obviously he's relatively new to Java, but hey, he could still be a performance expert.
(2) Never mentions the biggest weapon in the C vs Java arsenal. The thing is that C is only able to make optimisations up to a certain point, but there are optimisations that can only be made at run time not at compile time. So Java starts off behind, but can catch up some or even all of that distance.
People have used this to demonstrate Java code running faster than C code, but that is old news, a newb might not know this.
(3) Does not mention the second biggest weapon in Java's arsenal in the Java vs C speed argument. That being that more recent versions of garbage collection allow for super fast memory allocation - enormously much faster than what you get with malloc.
(4) Never quantifies how much slower Java is. If Java is 5 or 10% slower, then Meh, is that really news? If Java is 2x slower than carefully hand-tuned C by guys with actual code writing credentials like Linus, then that is still pretty good. 2x screamingly fast is still more than good enough for most people. If the difference is an order of magnitude, then that is not so good. If the difference is two orders of magnitude, then you might as well be using some scripting language.
A proper expert would certainly have quantified the speed difference.
In theory, any VM language will be slower than a memory managed app.
Then again, the dragon book is hard :)
In case if you are the expert, I would assume that he wouldn't mind if you have any insights to tune in the performance of JGit even further. You can start by joining in the mailing list.
He wasn't quantifying anything since he was not answering your question, he was sharing his experience.
As an example, the C# port of Sqlite is sometimes faster than the C version on queries, although updates are slower, despite Sqlite is a highly optimized C library.
EDIT: link http://code.google.com/p/csharp-sqlite/wiki/Benchmarks
Otherwise, unsafe regions have practically zero overhead, and they are close enough to the metal.
List<int> uses an actual array of integers as its backing store instead of an array of objects that contain boxed copies of the integers.
There is a port of JGit to .NET called NGit, which is what we use for MonoDevelop.
The port is maintained with an automatic tool that converts Java code to C# code, you can find it here:
Yes, that's what I meant with specialized collections :)
BTW, I didn't know about the awesome NGit, automatic conversion from Java is really impressive!
I heard about related stuff happening while I was working at a Smalltalk vendor. The programmer who was implementing the network cryptography library would just call up the VM engineer and ask for goodies like support for large bit arrays, and he'd get them in for the next VM release. The programmer was able to beat some of RSA data security's (poorly implemented) reference DLL's written in C by 3% with a Smalltalk program.
In the Smalltalk environments, it's easy to implement "primitives" coded in C, just in case you weren't internal to the vendor. With open VMs like Squeak, you can add your own bytecode to support optimizations if you want to.
It's not surprising to me that the C# version of that VM does as well (or better) for this particular codebase.
Money quote about half-way down the page: "SQL statements compile into virtual machine code"
Every SQL database engine compiles each SQL statement into some kind of internal data structure which is then used to carry out the work of the statement. But in most SQL engines that internal data structure is a complex web of interlinked structures and objects. In SQLite, the compiled form of statements is a short program in a machine-language like representation. Users of the database can view this virtual machine language by prepending the EXPLAIN keyword to a query.
The use of a virtual machine in SQLite has been a great benefit to the library's development. The virtual machine provides a crisp, well-defined junction between the front-end of SQLite (the part that parses SQL statements and generates virtual machine code) and the back-end (the part that executes the virtual machine code and computes a result.) The virtual machine allows the developers to see clearly and in an easily readable form what SQLite is trying to do with each statement it compiles, which is a tremendous help in debugging. Depending on how it is compiled, SQLite also has the capability of tracing the execution of the virtual machine - printing each virtual machine instruction and its result as it executes.
I strongly recommend you read one such book more or less end-to-end.
HN: As with other areas of computer science, there is often a "canonical" book, like TAOCP or CLRS on algorithms. Is there any such book for databases?
Bonus: in the sqlite command line tool, putting "EXPLAIN " before a query will dump out the VM commands that the query was converted to, without executing them.
Things have changed a bit since then, but not much. SQLite's API is very different than regular databases because it is a library operating in the same process. In particular it does not calculate all result rows for a query up front (that wouldn't be very 'Lite') but instead calculates the next matching row as you ask for it.
Consequently the internals have to be able to record their state, return a row, and then resume from that state to get the next matching row. There is a also a fair amount of query optimisation that goes on, which again means the need for expressing queries in a variety of different building blocks. Combine the state machine with building blocks and you have a special purpose VM.
That sounds like SQL cursors?
Virtually all other database engines calculate the query results up front. It is more effective in their implementations to do it that way. For example they will also calls to ask how many rows remain in the results. SQLite has no such API and the only way to find out is to actually retrieve each result row.
Shawn and I gave a presentation at the Googleplex not so long ago about JGit [1]. In particular, you may be interested in the 'JGit at Google' section.
There are some cases where JGit is faster than CGit, but the benefits of JGit are that it's easy to embed. There are projects like gitblit and other IDEs that use the library. On top of that, you have crazy folks like NGit [2] who cross compile the library using Sharpen so it can be used by the .NET community...
[1] - https://docs.google.com/present/edit?id=0ATM14GNiXaXfZGZkeHp... [2] - https://github.com/slluis/ngit
I'd love to hear more about any code changes that lead to this result.
So I doubt it has anything to with Java, but the underlying storage. If I'm wrong I'd also like to hear about it!
Has anyone measured it?
Many of us have already read this and it's been submitted to HN several times before. This, of course, does not mean that it's not worth reposting, but interested parties may want to dig up some of the past discussions.
EDIT: This obviously came across a bit as language fanboyism, so I guess I should mention that the language features that let you do many of them let you shoot yourself in the foot just as easily as you can in C, and you can certainly argue that with a strong FFI you might as well just call into C if you really need that kind of low level performance..
Won't using a high-level language incur an omnipresent speed slump? And even if a bottleneck exists, how would using a FFI remedy crucial problems in the language, like the absence of unsigned types or that all types are boxed. The types will have to be unboxed anyway, so whether that happens in foreign code or in the interpreter/JIT code won't matter.
like the absence of unsigned types or that all types are boxed
FFIs often provide access to C arrays.
Yes, but most programs don't require high performance everywhere - in a library like JGit for instance, most operations are probably plenty fast written in Java even for very large projects; it's likely only a few are problematic.
> And even if a bottleneck exists, how would using a FFI remedy crucial problems in the language, like the absence of unsigned types or that all types are boxed.
That's maybe an argument to allow more control over memory layout and machine representation in high level languages - although there are ways around this, like defining your data types as a C++ class and then providing a high level binding.
Primitive types have been available since the creation of Java. It's up to the programmer to use boxed types or not.
Memory mapped IO - see Java.nio.
Bump-pointer allocation/compilation to direct loops all exist in hotspot.
You can't use a primitive in a collection: eg HashMap / ArrayList
Not sure that is true. Just look at pypy(http://pypy.org/) which claims that run-time optimizations in the interpreted interpreter outperforms the C interpreter, and quite significantly in many cases. So I don't think it's true that high-level languages are always slower. It has a lot to do with the optimizations you can do at run-time. There is also an interesting paper on developing an OS based on run-time code synthesis for optimizing performance (http://valerieaurora.org/synthesis/SynthesisOS/). The major drawback of languages like C is that it can only optimize things at compile-time. I think as projects get larger and we move towards parallel structures and algorithms the need for languages that support run-time optimizations will be greeter.
Haskell has plenty of industrial users and quite a few very large programs as well. Those libraries hardly look mature - I know that many of the container libraries on Haskell make extensive use of unpacking, for instance.
Here is a good set of slides on Haskell and optimisation: http://www.slideshare.net/tibbe/highperformance-haskell
My guess is that this phenomenon reflects the fact that other language communities have greater comfort and facility with C libraries, or to look at it another way, the fact that complete independence from native libraries is actually a feasible goal for most Java projects.
- There's no way to do array access without null pointer and index checks each and every time.
- Generics with basic types, and their unfortunate embedding into syntax (like the new for() syntax), are awful. Boxing and unboxing incur a ludicrously high penalty, and generics push coders away from using arrays. Unlike in C++, generics have been the enemy of performance.
- Poor quality collections classes (ArrayList and HashMap are notoriously bad)
Sure there's a few other things like pointer walking etc. in C, and Java's poor floating point, but the big three above are the killers.
That's not happening anymore for years already.
Actually there is. Have you checked out the sun.misc.Unsafe class? Lots of very dangerous gems in there, among them the ability to calculate array offsets and access the array elements directly. (check out arrayBaseOffset + arrayIndexScale + getObject/getLong/etc)
Even the collection classes can change between JVM versions. I'm not saying that they're all necessarily better, just that different versions are different. So the Dalvik or IBM J9 versions might do what you want better.
Addition is XOR which is sign-agnostic. Multiplication has to be done via table lookups to be fast which also makes it sign agnostic.
Well, at least for p=2.
The questions are, if you had to develop a distributed version control system in java: a) would you solve it the same way, b) would your solution be faster or slower, and c) would it take more or less time to write it and be easier to maintain?
Clearly you would not solve it the same way, for example it might stick around in memory as you worked. Could it then appear faster, from a user's perspective? Possibly. Might it be easier to maintain. Also possible.
Pretty much by definition, if you are writing it in C, a binary compatible solution is not going to run as fast if you port it to java.
I don't think that is a conclusion that has much value.
The original goal of JGit/EGit was to provide an Eclipse plugin for working with software using the Git SCM. The Eclipse plugin is still the main goal of many of the developers, but we are open to anyone wanting to interface with other tools, Netbeans, Ant, Maven etc. For those, the JGit part provides a high performance API for working with Git repositories. The main other user of JGit, besides EGit, is Gerrit Code Review, which used by projects such as JGit (ofcourse), EGit (by implication) and Android.
Not sure if that provides a very good motivation, but there you go. :)
"But, JGit performs reasonably well; well enough that we use internally at Google as a git server."
He is not advocating to never use Java. He is just pointing out some things that may or may not be important when choosing a language to write a program.
Just curious about what advantages there are to make you sacrifice the performance of the cgit binaries. Mostly out of ignorance on the subject.
Also, related to source control but not Git, a few years ago Google had a tech talk about writing a Mercurial storage system on top of BigTable: http://www.google.com/events/io/2009/sessions/MercurialBigTa...
This tends to be my general philosophy, by the way. Reuse code to get something working fast, isolate what really causes bad performances, then solve only those problems by going under the hood. If the performance issues remain, cheat by pretending it doesn't exist, by making sure we're never in a worst case scenario and handling the worst case scenario differently.
In my "IntHashMap" case, the worst case scenario was gathering the keySet. I made sure that I'd only call it when I really really needed it. The rest was "fast enough" once I had removed the underlying Integer Object on the key.
Does anyone know why this is the case?
The memory you have is therefore allocated by the kernel of your operating system and oblivious to any garbage collector you might have.
Still, I wonder why they're using a MappedByteBuffer in the first place if they're working with the data in the Java heap.
I think that this is the key takeaway for the entire post.
One of the reasons I generally dislike any of the "X IS BETTER THAN Y" bakeoffs is that performance is now so implementation dependent that these comparisons are pretty much moot. Given that basically any non-trivial implementation can be improved, it's difficult to say that anything is faster, especially when one considers developer skill.
Developers should not be chasing the abstract, absolute best performance. Instead, the language used should be the one that delivers performance that is good enough for their client's needs. If they can get it with something that we're familiar with, that's great. If they need to learn a new tool, that's also good. But it doesn't make much sense to throw away all the knowledge that a developer has about a certain language to chase "better performance" with a different one. Most likely, the first effort implementations on a new language won't be nearly as good as the implementations on the more familiar language.
It's generally true that optimized Java won't ever be as fast as optimized C. But for the vast majority of cases, it doesn't need to. Java's speed is enough for those cases. And in the small minority where it's not sufficient, C is still around.
But for some perspective from a former Mercurial developer: lots of the more performance-sensitive code has already been rewritten in C. Rewriting the rest of it would simply be a question of diminishing returns. One thing that would improve is hg's startup time; starting up Python just takes a while, which kind of sucks for command-line programs like VCS clients that tend to have many short-running invocations.
Python starts up very fast for a language runtime (much much faster than Java). But yes, if you run a large amount of extremely short tasks, the startup might become significant I guess.
(a code review tool used by Android.)
https://docs.google.com/present/edit?id=0ATM14GNiXaXfZGZkeHp...
It gave an example where the compiler's knowledge that something is an immutable array means better optimization. Which you can't express in C.
Most code should not be C.
Be careful and stay standards-compliant and you can keep most of the portability and maintenance advantages while picking up some significant speed.
In reality this often requires a heavy refactor to actually work. In Java with JNI for instance the overhead of calling native methods is actually rather high, over 200 cpu cycles in many cases. The stack often has to be re-arranged, a CPU stall is usually caused and in the case of most data types passed to the native function, they have to be copied (last i knew java.nio buffers were the only types that weren't copied).
Point is, just moving your "hot function" to C / C++ and calling with JNI doesn't work unless that function is rarely called and does a lot of work internally. More often the "hot function" is something that is called thousands of times and moving something like that to JNI is just as likely to kill performance as help it. You'd have to abstract away an entire module of work and minimize its call surface to JNI to achieve your goal.