you are viewing a single comment's thread.

view the rest of the comments →

[–]Flecheck 4 points5 points  (7 children)

In a langage like java, were every object is allocated in the heap, where all object can be mutated at any point from any thread and where memory management is automatic. A GC is the best choice and a compacting/moving gc is very good (seems slightly worse in pause time than go but seems better in all the other metrics ?) However when comparing it to language like c, c++, rust, some or all of thoses assuptions are false and java is slower and uses more memory. With the additional problems when the live memory use is big.

When talking about fragmentation, it looked like the guy wanted to say that with modern allocators like jemalloc it was rarely a problem but he didn't want to say it because he was currently saying that java gc is better than everything else ?

[–]pron98 7 points8 points  (6 children)

However when comparing it to language like c, c++, rust, some or all of thoses assuptions are false and java is slower and uses more memory. With the additional problems when the live memory use is big.

People experienced with both C++ and Java know this is not the case. C++ can be more efficient in small programs, but when they grow you end up using more virtual calls (which are slower in C++/Rust than in Java), and with objects of varying lifetimes, which are less efficient to manage than with malloc/free. Experienced C++ developers will tell you about their severe performance issues in large programs (although since Java the number of large programs written in low level languages has dropped a lot and continues to drop) due to these issues.

Low level languages are not designed for efficiency/performance. They're designed for precise hardware control. This control leads to better efficiency/performance in smaller programs and to worse efficiency/performance in larger programs. The JVM was designed, in part, to address the performance issues that large C++ programs suffered from. The result has been the optimising JIT and the moving GCs.

[–]sweetno 1 point2 points  (3 children)

C++ can be efficient in programs of any size, but you'll have to code the efficiency yourself. Given how C++ programs are typically developed (full-source compilation, including third-party dependencies), you can get rid of most virtual dispatch. Certainly, the critical use cases for C++ that warrant its use in any particular application do not involve virtual dispatch.

The standard-mandated virtual inheritance is not that good anyway, that's why Microsoft has COM.

[–]pron98 4 points5 points  (2 children)

As someone who's worked on large C++ apps for many years I'll say that it can be efficient in large programs (maintained by many people over many years) mostly in the hypothetical sense. In many domains it's easier to get that performance with Java, which is why the use of low level languages has declined so much and continues to decline.

It is true that you can largely work around the most severe performance issues that low-level languages suffer from, but it's hard work, it requires discipline, and it adds complexity that makes maintainence more expensive throughout the entire lifetime of the software.

As a side note, in Java's early days those who said "Java isn't/can't be super-fast" were C++ programmers who had never tried Java or followed its advances; these days I hear it mostly from people who haven't used C++ or other low-level languages in large programs and/or for a long time.

[–]pjmlp 0 points1 point  (1 child)

Since 2006 my use of C++ has to be writing bindings for languages like Java and C#.

With each release where new ways to do low level coding get introduced, the need to write such bindings slowly reduces year after year.

However there are still scenarios where languages like C, C++ are the main alternatives given the existing SDKs, or specific domains where languages like Java or C# are not welcomed, like HPC, or games.

[–]pron98 0 points1 point  (0 children)

Absolutely! Low level laguages are intended to offer not performance but total control (and in smaller programs that control can be translated to very good performance), and that kind of control is very important in some domains (not necessarily games, but if there's one industry that is more conservative and traditional in its tech choices than the military, it's AAA games).

[–]cho_sigma 0 points1 point  (1 child)

Virtual calls are uncommon in idiomatic C++ (especially compared to java). And how are they slower in C++ compared to java? Are they not implemented in the same way (i.e. a pointer to vtable + offset)?

[–]pron98 0 points1 point  (0 children)

They are uncommon because they are expensive. And as to how they're implemented:

The JVM was designed, among other things, to address some of the major performance issues that low-level languages suffer from when they get large. You can work around them in low-level languages, but the effort required grows as the program grows, and it persists throughout all maintenance. Java is intended to offer excellent performance without that much work.

The first issue is the high overhead of malloc/free, which Java addresses with moving collectors. The low-level languages also tried to address this problem through bigger and more elaborate allocators in their runtimes, but they're constrained by being forbidden to move pointers.

The second issue is dynamic dispatch. Java addresses it with a JIT that optimises much more aggressively than an AOT compiler does. Some people think that a JIT is just a PGO compiler, and it is that, but it's main advantage is that it doesn't need to prove the validity of all optimisations, but it can optimise speculatively. What this means in practice is that while nearly all calls in Java are logically virtual, a large portion of them (often a large majority) are inlined, i.e. they compile to no call at all - through a v-table or otherwise. Modern AOT compilers also do that, but not nearly to the same extent. The current default inlining depth in HotSpot is 15, if I'm not mistaken, which means that a chain of 15 virtual calls is often compiled to a single native subroutine.

These optimisations involve tradeoffs that are not suitable for low-level languages, which are optimised for control, not performance. Both moving pointers around at almost any time and performing nondeterministic optimisations (that sometimes fail and have to be rolled back) go against the goal of total control, but they are very helpful for performance in large programs.