3

I know there is an opinion that programs written in Java and running under JVM as as fast as C++ programs, after introducing just-in-time (JIT). I see many cases when Java is terribly slow for simple programs compared to C++, i.e. about factor 2-3 slower than equivalent C++ programs.

Just give you a small example: This is simple benchmark code to merge particles in clusters:

https://github.com/chekanov/hepjet

Go to "scjet_cpp" and type "make; make run". This runs C++ code. Then go to "scjet_java" and type "make; make run". This runs the same code written in Java.

Th first C++ code finishes merging particles within 50 msec (i7 desktop). Exactly the same Java code finishes for 170 ms. There is nothing special about the code, i.e. identical implementation in C++ and Java.

Any comment?

6
  • 5
    Just noting a few small things like in c++, PI2 is a static const while in Java it is a private final - but not static. This has implications on optimizations that javsc can do. They are similar, but the Java code is not necessarily optimal. Java relies on jit to do more, which won't be visible in 50 or 100ms.
    – user40980
    Feb 10, 2015 at 1:48
  • 7
    A long time ago when I ran Java benchmark I often had to put in a few warm up runs. You didn't seem to have warm up iterations in your experiment at all. Secondly, did you run both on a 32bit version? or both on 64bit version? That can easily make some difference.
    – InformedA
    Feb 10, 2015 at 4:34
  • Can you also replace the use of vector in your C++ code with an implementation of linked list. Similarly, use LinkedList instead of ArrayList in the Java code and post the number here. Thanks.
    – InformedA
    Feb 10, 2015 at 5:56
  • And by the way, you can post your code at stackoverflow or codereview. I believe on stackoverflow, they are very good at reading low level code and find out area/lines where an aggressive compiler will do reduction. That can be another factor here as well.
    – InformedA
    Feb 10, 2015 at 9:24
  • 1
    Never trust a benchmark you didn't fake yourself... Feb 10, 2015 at 15:04

5 Answers 5

36

Your measurement is probably flawed.

  1. If you're just running both applications and measure the time from start to end in the terminal, you are not measuring execution time, but also startup time. For Java application, this means that those 170 ms. also contain JIT compilation.

  2. Moreover, 50 ms. vs. 170 ms. is not representative. Doing a task in a loop and obtaining 50 s. vs. 170 s. will be more relevant.

  3. You can't just take a single project and say: "This language is better, because this particular project executes faster with this language." A single project is not representative enough. Maybe authors knew C++ more than Java. Maybe they missed opportunities to optimize code.

  4. Compilers you use may make a huge difference. Some will create code with terrible performance. Others will use clever optimization techniques.

Also, what's your goal? To be able to say: "Java sucks, everyone should use C++?" This is not particularly constructive. A more constructive approach is to build an application using the technology your team knows best (say Java), then find the bottleneck and find that it can be removed only by moving this part of the project to low-level language (including Assembler).

8
  • 6
    +1. Java startup time includes loading, verifying, & initializing classes, then interpreting or JIT'ing. Once the JIT compiler collects data from multiple loop iterations, it can take advantage of actual usage patterns to do smart optimizations. The GC makes for fast allocation, freeing of short-lived objects, and cache locality at the cost of background computation work. Thus short programs pay a high start-up cost while long-running programs are competitive and also avoid memory fragmentation. Array bounds checks and null checks mean memory safety but slower jamming through data like pixels.
    – Jerry101
    Feb 10, 2015 at 2:14
  • 4
    This is a "flawed measurement" only to the extent that it's a poor reflection of the workload at hand. Using a short-lived program would be flawed if you really care about long-running server processes. Using a long-running program would be equally flawed if you really care about small utilities that you only expect to run for a short time. Feb 10, 2015 at 3:47
  • 1
    Can't resist to comment: great insight here. In practice, most real world scenarios run for minutes if not hours and days, in that macro view JVM will have very competitive performance.
    – InformedA
    Feb 10, 2015 at 4:08
  • 2
    @Jerry Coffin: true, but as a user I wouldn't care about a factor of 2-3 times when it's 50ms vs. 150 ms - I would not notice the difference.
    – piet.t
    Feb 10, 2015 at 7:38
  • 2
    @piet.t: That depends. Do you run lots of those in a batch / build-system? Do you have to wait one them? Those additional 100ms are quite noticeable and a severe degradation in most interactive scenarios. Feb 10, 2015 at 14:22
16

There is no one answer to this, largely because it's not always true. In fact, arguably it's never really true. You can't measure the speed of a language, only the speed of some particular implementation (and rarely even of the implementation as a whole, only of its execution speed on some particular piece(s) of code.

Java code can be slower than C++ code--sometimes by a factor much larger than 2 or 3.

Java code can also be competitive with C++ code--but often requires substantially more memory to do so.

For a few, very specific, things Java can be faster than C++ that's written similarly.

That said, yes, on average code written in Java will run slower than (roughly) equivalent code written in C++. If your primary concern is with execution speed, Java has a number of disadvantages.

The main one is that it depends heavily on a JIT compiler for optimization--but since the user is waiting while the JIT compiler runs, most JIT compilers don't include the most expensive optimizations. Since a C++ compiler typically runs with only a developer waiting (or nobody waiting, in the case of server-based builds with CI systems and such) it's much more reasonable for the compiler to include every optimization in the book.

A second factor is closely related: C++ vendors see their customers as being extremely concerned with speed. Java vendors almost certainly see their customers as being more concerned with things like features and fast development than with achieving absolutely the highest possible execution speed. That's not to say they can or do neglect execution speed entirely, just that they don't emphasize it to nearly the same degree as C++ vendors.

Garbage collection can have an effect as well. Java (normally) uses a concurrent, copying collector. This works reasonably well across a wide variety of work loads, and most JVMs have some "tuning knobs" to help out when the default setting aren't optimum.

Nonetheless, testing seems to indicate that a JVM requires substantially more memory than equivalent C++ code to even hope to run at (approximately) the same speed. Especially when you're running in a memory-constrained situation, the overhead from garbage collection can increase substantially. This typically stems less from how often the garbage collector runs (in itself), than from the fact that when it runs, many objects are still "alive". A copying collector works by copying "live" (reachable) objects into a new location, so ideally you want nearly all objects to be "dead" (unreachable) before it runs. With less memory, it not only runs more often, but each iteration takes longer because fewer objects have aged enough to "die", and more objects remain reachable (and therefore need to be copied).

There's also a difference in the amount of work that's done at run time vs. compile time. C++ places a high emphasis on doing as much as possible at compile time. For example, all the cool template "stuff" happens entirely at compile time (at the expense of horribly long compile times in some cases). Despite similar syntax, much (most?) of Java's enforcement of types in its generics system happens at run-time. Java also includes (and many Java programs use) such things as reflection, which simply aren't present in C++.

Java also adds a number of convenience features such as auto-boxing that make it fairly easy to add quite a bit of overhead to seemingly simple operations in ways that aren't immediately apparent. C++ has some of these as well, but (it seems to me) they're both fewer in number, and their detrimental effects are typically somewhat less drastic (but my opinion on that may well be affected by the fact that I know C++ quite a bit better, so to me, similar problems may seem much more obvious in C++ than in Java).

Bottom line: it's difficult to point to a single, specific reason that Java programs are slower than C++ programs. Although it's true much more often than not, the reasons for its happening vary widely, and in many cases it seems to be more the "death of a thousand nicks" than being the result of a single factor.

5
  • Java uses type erasure. All enforcement of generics occurs at compile-time, since the generic types essentially become typecasts in the output bytecode.
    – user22815
    Feb 10, 2015 at 16:48
  • @Snowman: It produces casts, yes. That doesn't mean it all happens at compile time though (or are you actually unaware that Java casts can produce run-time errors?) Feb 10, 2015 at 16:54
  • Yes, casts can produce ClassCastExceptions. But the type enforcement (e.g. bounds checking for ? extends T or T extends S) is done at compile time. Your answer is really good and I upvoted it, I just found this one little nitpick.
    – user22815
    Feb 10, 2015 at 16:56
  • @Snowman: I suppose the wording a bit on the poor side--in the case of a generic collection, you shouldn't ever see a ClassCastException generated--but with the way Java is defined, all the code to check the type and throw if necessary still gets executed, so you pay the price of run-time enforcement, even though it shouldn't ever happen. I'll try to think of some way of rewording it to be more accurate without getting excessively verbose. Feb 10, 2015 at 17:13
  • A big factor as well is the garbage collection can take up a significant amount of resources while running and performance takes a hit during that time. So you need to employ all kinds of tricks to keep GC spikes to a minimum such as greedy allocation of mem, circular buffers etc
    – Dominic
    Nov 8, 2020 at 6:57
1

As to why Java can be slower well:-

  • The JVM needs to interpret pseudo machine code, C++ blindly executes native machine code.
  • The JVM manages memory, C++ its up to the programmer to manage memory which is more efficient but also more error prone.
  • The JVM needs to start up, then load and interpret the various classes; with a C++ program load is basically copying the instructions from disk to memory, in many cases (with DLL, .so, shared libraries etc.) the program will have already been loaded and is sitting in memory ready to go. This is actually the biggest performance hit with Java and makes the language totally unsuitable for short running independent executions.

While java may be up to three times slower it is often almost as fast as C++. You have to ask yourself if this really matters when you are running on a 3ghz processor which is spending most of its time waiting for memory to be loaded (or worse disk IO).

The other side of the equation (highly subjective, but, backed by years of experience) is that Java programs can be developed up to three times faster. The toolsets are more functional, there is a vast selection of really good libraries to choose from, and a limited number of gotchas to look out for (no if (a = b), no mixing up pointers with the pointed to).

Personally where I feel the need for speed I code in plain C. Its a language I know well and the performance constrained parts of a system tend to be small and well defined.

So its C for speed. Java for production code. Groovy or Python for everything else!

1

To avoid speaking on false premise here, I would like to see at least part of the disassembly generated from the hot spots (tight loops where the CPU spends most of the time) from the two programs, before I would say anything substantial here.

For Oracle JVM, the following option can be used to save a copy of the machine code generated from JIT:

https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly

For C++ running on x86, I recommend AMD CodeAnalyst on Windows, and objdump on Linux.

One should take close look at:

  • The function calls (those that haven't been inlined)
  • The loops (to see how they are nested, and how many times they are executed)
  • The use of floating point instructions
    • Whether it uses the old x87 stack or the new SSE2 scalar/vector floating point instruction.
    • Use of expensive floating point instructions, and evidence of any optimization techniques done manually by each program's original author.
    • Use of library calls for handling math functions

If you do not already know the hot spots of the program, use a Low Overhead CPU Profiler to identify them first. Make sure not to confuse with high-overhead profilers. They are meant for different purposes.

1

I'm very glad that I see professional answers. You are all smart! Indeed, there is a small check you can do to test that this long runtime in case of Java is due to JIT compilation. This is how to see this:

Envelope these lines inside scjet_java/KT.jet as

for (int i=0; i<1000; i++){
    KT KT = new KT(0.6, 1, -1, 5.0);
    KT.setDebug(true);
    KT.buildJets(list);
    KT.printJets();
}

This will run particle clustering 1000 times. You will see that first run takes about 75 msec (i7,64 bit), while the next ones take only 15 ns for one clustering step. 15 ns is exactly the time that is needed for C++ code inside scjet_cpp/main.cpp to run over the single event!

So, the runtime is similar in C++ and Java code. I do not care much about 10-20% difference, if any at this level.

Not the answer you're looking for? Browse other questions tagged or ask your own question.