Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I think it's also a matter of experience - someone used to writing code in unsafe low level languages has a different approach to solving problems and may find Rust gets in the way. I actually started with C++ and after writing a reasonable amount of it I found myself wondering why I had to keep track of lifetimes, nullability etc in my head when it was so easy to mess those up. I kind of discovered "why Rust" from first principles and from then on I was hooked.

I'm not sure I understand your point about dynamic dispatch being slow in Rust/C++ or shared objects? If you're targeting native (which I find important) neither Java or C# are going to be faster surely. Maybe if you're willing to run Java/C# JIT you might find some wins (skeptical it's faster across the board) but you also don't need dynamic dispatch in performance-critical areas. I rarely reach for a Box<dyn Something> even in my high-level code.

I haven't worked on large Rust projects (> 500k loc) so I can't speak to the maintenance costs of that, but for me it doesn't matter (at least yet).

But we see even experienced professionals making mistakes with low level languages and I think it's worth considering if it's worth some of the cons you bring up to avoid those. Kind of reminds me of Carmack talking about static code analysis years ago:

https://archive.is/qC9a

> The more I push code through static analysis, the more I’m amazed that computers boot at all.



> I kind of discovered "why Rust" from first principles and from then on I was hooked.

I get it. The language certainly does appeal to some people, and I can understand why, just as I understand why it does not appeal to others.

> I found myself wondering why I had to keep track of lifetimes, nullability etc in my head when it was so easy to mess those up.

And I agree with that, but my conclusion (after decades of experience with low-level programming) is somewhat different: Don't reach for a low-level language unless precise low-level control over the hardware is the exact thing you're after. And when that is the case, I find that safe Rust doesn't offer the control I need, and unsafe Rust (and/or a lot of custom code) is not what I want to use.

> Maybe if you're willing to run Java/C# JIT you might find some wins

Of course I use the JIT. That's exactly what it's for. Now, I don't care if the buffer from which the CPU reads instructions is memmapped from a file or generated by a JIT, but I do know that some people like the "single native file experience". To that end, we're working with Google to add a small feature to the JDK that would allow it to link the JVM, other native libraries, and Java classes into a single native executable (it's still going to JIT the Java code, but you'd be launching a "native binary").

> (skeptical it's faster across the board)

I wouldn't say it's faster across the board. You sometimes can write large programs in C++ (or Rust) that match and even exceed Java's performance, but it gets harder and harder the larger the program is. On average, I find that the "effort per performance" is, on average, significantly lower in Java in large programs. And it's not just the JIT. Another weak point of low level languages is that their pointers can't move, which means they can't use moving collectors, which also offer superb efficiency, again, mostly in large programs when you have lots of objects of varying sizes and lifetimes (especially now when we no longer have GC pauses).

> but you also don't need dynamic dispatch in performance-critical areas

You certainly don't start out needing it. Over time, however (and important codebases last at least 15-25 years), it either creeps in or it affects sufficiently many less critical paths to make an impact. You can try and re-architect things, but it takes a lot of effort (and it's this evolution effort that was a major reason for C++'s decline).

> But we see even experienced professionals making mistakes with low level languages

Absolutely, but my prescription would be to avoid low level languages altogether, and that has indeed been the industry's trajectory, and it's continuing. And when you absolutely do need to kind of control that low level languages offer, language complexity can also cause (or help hide) mistakes in code that is often very subtle, and the added safety, which is partial at best in those situations, isn't enough to offset that. Again, this isn't universal, but there are reasons to avoid Rust in low level code that are just as good as the reasons to pick it, and so different people will choose differently.

BTW, I've never worked on a browser, and it may well be the Rust is the best language for that, but I would be very curious to try Java. First, modern browsers run a lot of JS so you have a JIT and a GC, anyway, and so it might be both easier and more efficient to have everything use the same GC, and while process isolation would have required Java to re-JIT the rendering pipeline, Java is about to allow sharing JITted code (and even caching it from one run to the next) so that there would be no need to warm up the same code over and over.


I think the only argument I’d make about high level programming languages is that software continues to outpace hardware development in sucking up as much performance gains as possible. One program written in a slower, garbage collected, high level language is ok, when they are all it’s bad. I think eventually we’ll get to a point where we won’t have to think about memory management anymore but we aren't really there yet. Heck, software written in C++ like browsers are dog slow, imagine if they were written in Java…


I originally came to Java because of the better performance it offered compared to C++ in large programs. The JVM is specifically designed to remove some of the fundamental performance overheads that low-level languages suffer from, and manifest especially when programs grow large (and a browser is quite large). So when someone talks to me about "GC languages" being slow and low-level languages being fast, I know they've not had much experience with either Java or low level languages, nor do they understand modern compilers and memory management. Java offers strictly more optimisation opportunities than low-level languages, in compilation as well as in memory management. What it gives up in exchange is low-level control (including worst-case performance), but it is low-level languages that sacrifice performance (especially average-case performance) in exchange for the control they need. Not being able to move pointers freely and not being able to deoptimise and recompile at runtime are serious impediments to modern optimisation, but low-level languages gladly give that up because they're not optimised for performance but for precise control.

In particular, modern moving collectors were designed for the purpose of removing the high overheads of malloc/free allocators that make heap memory management so expensive in low-level languages (and in any language that uses non-moving memory management strategies). The reason code in low-level languages tries to avoid things like heap allocation and virtual dispatch on the hot path is not because these things can't be super-fast (most virtual calls in Java are faster than many static calls in C), but because they are slow in low level languages because of their constraints.


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).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: