← Back to context

Comment by slopinthebag

10 hours ago

I don't think it's true that Java offers strictly more optimisation opportunities than low-level languages, but rather different optimization opportunities. C++ and Rust have other opportunities that Java does not generally have:

- Explicit object lifetime and deterministic destruction.

- Stack allocation by default.

- Value types and direct embedding of values in data structures.

- Precise control over data layout, alignment, padding, and SIMD-friendly representations.

- Explicit allocation strategies: arenas, pools, slab allocators, region allocators, custom allocators, memory mapping, etc.

- No mandatory tracing, write barriers, object headers, or garbage-collector scheduling.

- Native interoperation without an FFI boundary.

- More predictable latency behavior.

- Compile-time specialization through templates in C++ and monomorphized generics in Rust.

Rust’s ownership and borrowing model can also provide strong non-aliasing and mutability guarantees in safe code which are useful for optimization.

Saying that "Low-level languages sacrifice performance for control" is also not true imo, since they can avoid allocation entirely, store data contiguously rather than as individually allocated objects, avoid all gc work, control cache behavior and eliminate pointer chasing, and importantly, guarantee hard or soft latency bounds.

Saying that "Most virtual calls in Java are faster than many static calls in C" is not a meaningful claim either. I think? Cuz "it depends" :)

And remember that GC is not free. Objects must eventually be traced. Reference writes may require write barriers. Objects have headers and alignment overhead. Heap size must often be larger to maintain throughput. Concurrent collectors consume CPU and memory bandwidth. Stop-the-world phases or other latency issues remain even with modern collectors. Object movement can complicate native interoperability and pinning. Malloc can cause performance issues but Java is not beating an arena allocator in C++ or Rust.

Keep in mind speed and throughput are not the only performance metrics. So is startup time and memory footprint, where Java loses badly here.

I wish we had real published research to go off of here but real world examples are all we really have. I'm not seeing AAA game studios building their engines in Java. I don't see any OS's building their kernel (or anything really) in Java. If Java is faster than low level languages, why is that?

And look, I don't dislike Java or the JVM, and I'm actually a really big fan of C#. I just like Rust more.

> Stack allocation by default, Value types and direct embedding of values in data structures.

This used to be the big one, but not anymore: https://openjdk.org/jeps/401 (so Valhalla is integrating in JDK 28; it's not complete and this JEP is only the first step, but Java will have everything it needs on that front very soon.

> Precise control over data layout, alignment, padding, and SIMD-friendly representations.

Since the JVM controls layout, this is a point for Java.

> No mandatory tracing, write barriers, object headers, or garbage-collector scheduling.

Java doesn't require these things. JVM implementations choose to have them as an optimisation.

> Explicit object lifetime and deterministic destruction.

Hypothetically, this could have been an optimisaton opportunity. In practice, this is not a problem for Java but it is a problem for low-level languages, and especially Rust. The problem isn't knowing when an object's life is over, but needing to do something about it then and there. The whole idea of moving collectors (and arenas) is that it is more efficient to do nothing when an object becomes unreachable, and knowing when that happens doesn't help.

> And remember that GC is not free

Of course it isn't, but moving collectors are cheaper than free-list-based approaches, which is why we use them (most objects are never traced; those that are, are traced rarely etc.). On the whole, moving GCs are a speed improvement, reducing the overheads in C's runtime, and what they take in exchange is footprint (i.e. they use RAM chips as hardware accelerators). Now, that footprint cost could be expensive in smart watches and smaller devices, but in larger ones, the tradeoff is almost always worth it. I gave a talk about exactly that at Java One that should be up on YouTube eventually.

> Saying that "Low-level languages sacrifice performance for control" is also not true imo, since they can avoid allocation entirely, store data contiguously rather than as individually allocated objects, avoid all gc work, control cache behavior and eliminate pointer chasing, and importantly, guarantee hard or soft latency bounds.

I don't agree with that, and that's the very crux of my point. What you're really saying is that fine-grained control over the hardware lets a user who works hard enough to optimise their program to any level they choose. This does work well in small programs, but it fails in larger ones, and the JVM is designed to solve the performance issues that we C++ programmers experience in large programs. Over time, as programs grow and become more elaborate, it gets harder and harder to do those manual optimisations, which are very intrusive. E.g. you could perhaps use arenas in Rust more-or-less safely, and maybe Rust will make it easier in the future (right now the only language that makes that easy is Zig), but changing from no-arena to arena or vice versa, or finding out that you need special cases to some objects, requires a huge change to a low-level program. Similarly, trying to keep virtual calls to a minimum is very easy in the beginning, but becomes harder over time. So the idea that with enough control you can do anything is true in principle, but in practice it's very hard as programs evolve. The idea of modern runtimes is that you write the code naively, and the runtime performs global optimisations that help the average-case performance.

> I'm not seeing AAA game studios building their engines in Java. I don't see any OS's building their kernel (or anything really) in Java. If Java is faster than low level languages, why is that?

Much more performance-sensitive software is written in Java than in C++ these days, and your question assumes that the main thing that's important in these particular is speed, but that is not the case. What is the main thing kernels need to do? Directly control hardware. And what is the one thing that low-level languages are optimised for? Direct control over the hardware. Low-level languages fit the domain of OS kernels like SQL fits data queries; that's what they're for.

As for games, first, performance isn't the issue here. The most performance-critical parts of a game are not written in C++ but in CUDA, and the main important part for the CPU is a good algorithm for scheduling the data to the GPU, and that can be done in any language. What is very important for games is hardware support, and the JVM simply doesn't target most consoles. Second, games do care about latency, at least up to the length of a frame, and until very recently Java had GC pauses, and those could sometimes exceed the latency needed by games. GC pauses were removed in HotSpot only 3 years ago. So these are the reasons game engines are normally written in C++ (except, of course, for the most successful game in history, which is written in Java).