> What does .NET have that is better than Java for performant code?
CIL bytecode, besides what JVM bytecode exposes, provides much lower level access. As a result, C# and Java are languages of different categories and weight classes completely as of 2025. C# is a proper systems programming language in all the ways Java is not. JVM bytecode is also a more difficult to optimize target because all calls are virtual by default and type information is erased. OpenJDK HotSpot has to perform a lot of work just to get to the baseline where CIL starts at, where you have to explicitly make a call virtual and where type information propagates through generic type arguments. OpenJDK used to have a much more powerful compiler but .NET has closed the gap in almost every area, and in other it has surpassed it, or will surpass in the upcoming release(s). It also has a world of optimizations for structs and struct generics which are monomorphized like in Rust, something OpenJDK might only get to in the future. As I said in my previous comment, performance ceiling of .NET is at approximately the level of C/C++/Rust/Zig. Somewhat lower due to compiler limitations but the gap is small enough for it to be easily competitive.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/... is a good demonstration of performance difference in optimized code in these two languages (note that on <1s execution time benchmarks the startup impact also works against both, so you could look at the comparison between C# AOT and Go to get another picture)
Here reverse-complement and spectral-norm have >25% difference. When you look at the main comparison table for either, it might seem that C# lags behind natively compiled languages, but if you look at the NAOT numbers - it is right there next to them.
You've never actually verified this claim, have you? Next time - please do it.
- structs, with auto, sequential and explicit layout, with complex SROA and promotion handling
- stack-allocated buffers, unsafe fixed and inline arrays, even managed objects can have their layout configured
- monomorphized struct generics compiled the same way they do in Rust
- raw and byref pointers with pointer arithmetic
- portable and platform-specific SIMD, platform-specific intrinsics
- zero-cost interop which enables calls into malloc/free at the cost of C/C++ (you do not actually need this - there is a managed reimplementation of mimalloc which is fully competitive with the original)
- static linking with native dependencies when using nativeaot
In C, I can just write the whole program without calling malloc. I write my own allocator tuned for my programs behavior, I even have multiple allocators if needed. I just have a lot more control available. If you can't do this in a language, you can't claim its performance is close to C. Performance is about control.
You keep ignoring the point. Show me how can I write a program in C# that works with a fixed size memory region, lets say 512KB. It should not do any dynamic memory allocation.
// Please, just allocate a normal array instead, there is no benefit to this if it lives throughout the whole application lifetime
var bytes = (stackalloc byte[512 * 1024]);
// or, but, please, at least use a span instead of C nonsense of tracking offsets by hand
// it's not 1980 anymore
var ptr = stackalloc byte[512 * 1024]; // byte*
// or
[InlineArray(256 * 1024)]
struct CommandList { byte _; }
var commands = new CommandList();
// or
unsafe struct State
{
public fixed byte CommandList[256 * 1024];
}
Whichever you like the most, they do have somewhat different implications - stackalloc is C alloca while struct-based definitions have the exact same meaning as in C.
To avoid running into stack space limitations, you can place a fixed buffer or any of these large arrays into a static field. It gets a fixed static offset in the memory.
C# goes much further and also provides zero-cost abstractions via struct generics with interface constraints. Or without - you can also apply pattern matching against a generic T struct and it will be zero-cost and evaluated at compilation (be it with RyuJIT or ILC (IL AOT Compiler)).
CIL bytecode, besides what JVM bytecode exposes, provides much lower level access. As a result, C# and Java are languages of different categories and weight classes completely as of 2025. C# is a proper systems programming language in all the ways Java is not. JVM bytecode is also a more difficult to optimize target because all calls are virtual by default and type information is erased. OpenJDK HotSpot has to perform a lot of work just to get to the baseline where CIL starts at, where you have to explicitly make a call virtual and where type information propagates through generic type arguments. OpenJDK used to have a much more powerful compiler but .NET has closed the gap in almost every area, and in other it has surpassed it, or will surpass in the upcoming release(s). It also has a world of optimizations for structs and struct generics which are monomorphized like in Rust, something OpenJDK might only get to in the future. As I said in my previous comment, performance ceiling of .NET is at approximately the level of C/C++/Rust/Zig. Somewhat lower due to compiler limitations but the gap is small enough for it to be easily competitive.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/... is a good demonstration of performance difference in optimized code in these two languages (note that on <1s execution time benchmarks the startup impact also works against both, so you could look at the comparison between C# AOT and Go to get another picture)