16 ms·
I've seen the posts about all the speedups each new version of .NET gets and I'm just wondering, was .NET just alright in performance before all this? Is that t
by sp33der89 5y ago
I've seen the posts about all the speedups each new version of .NET gets and I'm just wondering, was .NET just alright in performance before all this? Is that the reason they can get all these speedups? :P
I'd be interested in some JVM vs .NET 6 benchmarks too, which platform to chose when.
EDIT: I know about the benchmarks and I also feel like sometimes these benchmarks are really optimized in a non-idiomatic way. I would love to know how performance idiomatic Java/.NET code is and if one is to start a new project today why one would choose the JVM over .NET or when someone would chose .NET over JVM.
- Semaphor 5y agoIt’s not the same, but there is this well-known framework benchmark [0], it always had the .net frameworks close to the top. I’m guessing a lot of the speedups come from getting rid of legacy cruft. With .net core/.NET 5/6 they got rid of a lot of things compared to .NET Framework 4.8 and could play with optimizations that simply weren’t doable before. That’s just me guessing, though ;) [0]: https://www.techempower.com/benchmarks/ https://www.techempower.com/benchmarks/
- foepys 5y ago.NET Core also introduced some new CLR features that are incompatible with the CLR used in .NET Framework. Span<T> for example. This has happened before with .NET Framework 2, 3, 4 etc. but instead of making a .NET Framework 5 they rather made .NET Core cross-plattform and threw backwards-compatability-at-all-cost out of the window. While all .NET Framework applications (except the ones that do naughty stuff with reflection) that were compiled for .NET Framework 4.5 behave the same way on .NET 4.8, .NET (Core) got rid of this and lets developers bundle the CLR directly, giving them more leeway for incompatible changes.
- rndgermandude 5y agoIt's that in part. Here are some of my additional observations and or guesses. They invested a lot of time adding language features with compiler and runtime support to avoid e.g. heap allocations/copying, like Span<> and friends, (readonly) ref structs, in/ref/out parameters (ref and out parameters existed before but were used a lot less in the runtime), or ValueTasks to some degree. This in turn enabled a lot of potential for optimizations in the compiler (aside from essentially writing an entirely new bytecode compiler with Roslyn and entirely new JIT with RyuJIT, throwing out the crufty old compilers), in the general runtime, and in the specific runtimes/frameworks e.g. ASP.NET. Those optimizations have to be implemented first however, and more and more get implemented with each new version. I have a project I maintain that sees an almost 50% speedup from net48 to net5, and another 10-15% speedup from net5 to net6 (based on the time it takes to run the extensive test suite). It isn't even that compute heavy. From profiling it appears that a lot of these speedups are due to internal copies of data being avoided, and a lot of additional fast-paths in the runtime (e.g. fast-paths for byte-arrays or character-arrays as opposed to taking the generic array slow paths). Another thing of note is that they added a lot of `bool Try*(..., out result)`-style APIs meant to avoid exceptions and the associated handling, and switched a lot of internal code to use these functions. E.g. in the reference source of the net48 runtime I think there are still a lot of instances of try { var number = int.Parse(value); } catch { // slow path/error path } instead of the new-idiomatic .netcore and later style of if (!int.TryParse(value, out var number)) { // slow path/error path } try-catch was/is slow-ish, and throwing exceptions is too, aside from it preventing inlining by the JIT a lot of times. And while #nullable (source annotations for what is nullable or not) and associated annotations such as MaybeNullWhen() had no direct influence on how the compiler could optimize, it probably helped people a lot writing correct code and as a side effect a lot more code became compile-time provable non-nullable which enabled further optimizations e.g. generating code that skips redundant null checks.
- chrisoverzero 5y ago> the new-idiomatic .netcore and later style `Int32.TryParse` has existed since .NET Framework 2.0, which was released on February 13, 2002: https://docs.microsoft.com/en-us/dotnet/api/system.int32.tryparse?view=netframework-2.0 https://docs.microsoft.com/en-us/dotnet/api/system.int32.try...
- rndgermandude 5y agoRight, this one has, a lot of other public or runtime-internal Try* methods have not. And even tho this particular one has existed for a long time, that doesn't mean it was used consistently in the runtime or in the popular first and third party frameworks. I'd argue the Try*-style, while artifacts of it were present before already, only really became widely idiomatic with dotnetcore.
- cyral 5y agoSurprising that in 2021 Java still doesn't have this in its standard library
- native_samples 5y agoWell, the Java approach is philosophically somewhat different. C#/.NET is closer to C++ where they are very willing to complicate the language and APIs to make the job of the runtime or compiler easier. Java just philosophically refuses to do that, more or less (perhaps you could argue that's changing a bit now with value types). So in Java they just made exceptions really fast. There are lots of runtime optimizations around exceptions, for example, if you regularly parse strings that aren't numbers then the resulting exception will automatically stop having its stack trace filled out, which makes throwing drastically cheaper. The JVM can also inline the code and then convert try/catches to ordinary gotos.
- CornCobs 5y agoHonestly I really like C#'s outvar and return success idiom. It's soooo ugly and yet slick at the same time. C does the same thing but I think the inline out var declarations make a huge difference to using them. Of course you miss out on error context an Exception or Result<T, Err> gives you but for many of the Try* functions it really doesn't matter.
- 9wzYQbTYsAIc 5y ago.NET used to be considered a little bit slow, especially when using the Reflection related features, but not nearly as slow as interpreted languages.
- gameswithgo 5y ago.NET has been faster than Java on most of the benchmarkgame benchmarks for a while, since .net core 3 or so. More specifically though the JVM has tended to be better about optimizing naive code than .net while .net has tended to offer more tools to do your own optimizing (unsafe, simd, value types, etc). So it would be interesting to see if the performance of naive code has improved relative to Java lately
- sp33der89 5y agoYeah, I would love it if I could just write idiomatic code for the platform and it'd be just fast enough!
- kasperni 5y ago> .NET has been faster than Java on most of the benchmarkgame benchmarks for a while, since .net core 3 or so. And which benchmarks games are those? If I go to to the Techempower benchmark and select only C# + Java. Java comes on top in every individual category of all the benchmarks. I'm not claiming that Java is faster than .NET. Just that I don't believe one platform is significantly faster than the other. [1] https://www.techempower.com/benchmarks/#section=data-r18&hw=ph&test=json&l=zik0vx-sf https://www.techempower.com/benchmarks/#section=data-r18&hw=...
- grumpyprole 5y agoSuch programs are often specially and painstakingly constructed to avoid all the commonly used language features that are inefficient. For example, in Java, user-defined data types are heap allocated and generic code boxes everything, even primitive types (an ArrayList of ints becomes unfortuately an array of pointers). Are these programs benchmarking typical idiomatic Java, or just some subset of the language?
- CraigJPerry 5y ago>> Are these programs benchmarking typical idiomatic Java https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/csharp.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... c# regex redux - 1.42 seconds java regex redux - 5.31 seconds Ok... but looking at the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/regexredux-java-3.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... import java.io.*; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.Map.Entry; import java.util.function.*; import java.util.regex.*; import static java.util.stream.Collectors.*; ... It's only using vanilla Java features. c# ? ... using System.Runtime.InteropServices; ... Interesting, why does it need that? [DllImport("pcre2-8", EntryPoint = "pcre2_compile_8", CharSet = CharSet.Ansi)] extern static IntPtr PcreCompile(string pattern, long length, uint options, out int errorcode, out long erroroffset, IntPtr ccontext); [DllImport("pcre2-8", EntryPoint = "pcre2_jit_compile_8", CharSet = CharSet.Ansi)] extern static int PcreJitCompile(IntPtr code, uint options); [DllImport("pcre2-8", EntryPoint = "pcre2_jit_match_8", CharSet = CharSet.Ansi)] extern unsafe static int PcreJitMatch(IntPtr code, byte* subject, long length, long startoffset, int options, IntPtr match_data, IntPtr mcontext); [DllImport("pcre2-8", EntryPoint = "pcre2_match_data_create_8", CharSet = CharSet.Ansi)] extern unsafe static IntPtr PcreMatchDataCreate(uint ovecsize, IntPtr mcontext); [DllImport("pcre2-8", EntryPoint = "pcre2_get_error_message_8", CharSet = CharSet.Ansi)] extern unsafe static int PcreGetErrorMessage(int errorcode, StringBuilder buffer, long bufflen); [DllImport("pcre2-8", EntryPoint = "pcre2_get_ovector_pointer_8", CharSet = CharSet.Ansi)] extern unsafe static IntPtr PcreGetOvectorPointer(IntPtr match_data); [DllImport("pcre2-8", EntryPoint = "pcre2_substitute_8", CharSet = CharSet.Ansi)] extern unsafe static int PcreSubstitute(IntPtr code, byte* subject, long length, long startoffset, int options, IntPtr match_data, IntPtr mcontext, byte* replacement, long rlength, byte* outputbuffer, out long outlength); Aha! It's because the c# impl is really just a wrapper round a native C impl of the problem. In what world is this a useful comparison? The fastest "real" c# solution is still faster than the java one though: c# (real) - 3.1 seconds https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/regexredux-csharpcore-5.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
- oaiey 5y agoThere are two things: (1) .NET Framework was slow and had some bad habbits (e.g. heap allocations, reflections, little optimizations, etc) ... especially the web stack. .NET Core/.NET fixes that, issue by issue. And since .NET is historically very close to the underlying platforms, we now see competitive outcomes (to e.g. Go, C++, etc). (2) Performance = lower CPU/Memory Allocation = more throughput = lower Cloud costs. At scale, that makes a huge difference.
- fnord123 5y agoSoftware benchmarks are super subjective. Michael Larabel at Phoronix and Isaac Gouy of the benchmark game have done a lot in this area. But everyone says you need to take it with a gain if salt (which is often true). There's also TPC-C benchmark suites where people benchmark their own software and claim results. Not really independent journalists there.
- Rochus 5y ago> Software benchmarks are super subjective. No, they are not, but they are just a measurement tool, not a source of absolute truth. When I studied engineering at ETH we learned "Who measures measures rubbish!" ("Wer misst misst Mist!" in German). Every measurement has errors and being aware of these errors and coping with it is part of the engineering profession. The problem with programming language benchmarks is often that the goal is to win by all means; to compare as fairly and objectively as possible instead, there must be a set of suitable rules adhered to by all benchmark implementations. Such a set of rules is e.g. given for the Are-we-fast-yet suite (https://github.com/smarr/are-we-fast-yet https://github.com/smarr/are-we-fast-yet).
- fnord123 5y agoIt's subjective because it can't be used as a source of truth. Of course "I measured X and my results were Y using methodology Z" can be a statement of fact but X and Z are where the subjectivity lie. For example, benchmark game allows for warmups and so does awfy. This favors jits because it allows them to warm up when they would otherwise be slower. This might give the mistaken impression that java is a great choice for command line tools due to the performance characteristics. In contrast, most benchmarks I've seen don't use profiler guided optimizations for C or C++. Hence the subjectivity. And the claim of only wanting idiomatic code in awfy. This is, of course, subjective as well.
- kaba0 5y agoLast I checked benchmark games didn’t care about warmups.
- jb_s 5y agoC# was never really slow in the same way as python, etc. And anyway 99% of the time you're gonna be slow because the sack of meat writing the program screwed up some aspect of the system design or the code. Unless you're using something really shit-tier for perf. The gap closed significantly with .NET core, which is why everyone was quite surprised when .NET 5 (the next iteration) had a fairly significant speedup in many scenarios. For reference, Stack Overflow was running on .NET MVC on like 2 servers pretty recently (with some auxiliary infrastructure for CDN and search) and using MS SQL.. I think it might still be running on this setup but not 100%. Honestly I have no idea how they do it on a .NET monolith but there you go.
- deleted 5y ago[deleted]
- foepys 5y ago> Honestly I have no idea how they do it on a .NET monolith but there you go. Low latency in a single rack can work wonders for performance. All those cloud services talk to each other over miles of cables and if you can slash latency to submillisecond regions, you get less wait time and free resources quicker. If you don't distribute your state across multiple Microservices, you can also save quite a bit of overhead. Plus hardware is just wicked fast nowadays and SO has a model of millions of reads for a single write.
- orra 5y ago> Stack Overflow was running on .NET MVC on like 2 servers pretty recently (with some auxiliary infrastructure for CDN and search) and using MS SQL. They are absolute beasts of machines. But yes, it’s incredible what you can do with just a few colocated physical servers. It suggests to me lots of dev shops pay the IaaS or PaaS cloud tax, long before they would have hit any scaleabilty walls with a non-cloud setup.
- danachow 5y agoA poster above claimed the servers were “48 logical cores (2 x 12-core Xeons) and 64GB RAM”, which really isn’t what I would consider such a “beast” of a machine when the RAM is in laptop territory, and a modest number of cores for a server.
- thrower123 5y agoOne SQL query going over the network will so dominate any micro-optimizations in the framework that it's a little silly for most of us to listen too closely when the ASP.NET team says they've sped up request processing another 40%. If JSON parsing request bodies and reading headers are significant, an API generally isn't doing very much.
- txdv 5y ago.NET has the ability to handle your memory layout explicitly with structs. They expanded on that functionality with span and made sure that the common libraries is implemented using this. If you do textbook OOP development for everything, you will end up with a lot of allocations and what not, which was the case, so they went through the entire base class library and rewrote all often used methods to be faster. they even posted a bunch of posts with all their improvement tricks: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/ https://devblogs.microsoft.com/dotnet/performance-improvemen...
- DeathArrow 5y ago>If you do textbook OOP development for everything, you will end up with a lot of allocations and what not, which was the case, so they went through the entire base class library and rewrote all often used methods to be faster. They forgot to tell the others that there is life beyond OOP and GoF design patterns.
- mdoms 5y agoC# is filled to the brim with functional programming features. Much of the base class library is somewhat functional (although obviously not all or even most, given the age of the BCL and stability of the API).
- Bayart 5y agoMost of the performance gains are really in the middleware (for example Entity Framework) and getting rid of pre-.NET Core legacy cruft rather than the VM, AFAIK.
- CyanLite4 5y agoNot exactly. Here’s a really good blog post on the perf improvements: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/ https://devblogs.microsoft.com/dotnet/performance-improvemen...
- bob1029 5y ago> was .NET just alright in performance before all this For some niche applications (i.e. financial exchanges), .NET 5 [was/is] arguably the fastest way to implement certain ring buffer abstractions because of its interesting blend of performance and safety. There is a variant of the LMAX Disruptor developed for .NET which leverages the value semantics of the C# struct to push things beyond what the Java implementations are capable of [0]. Certainly, with enough resources and manual memory management, you could best the C# implementations using a C/C++/ASM codebase, but this is a tenuous tradeoff with practical risks that must be accounted for. [0] https://medium.com/@ocoanet/improving-net-disruptor-performance-part-3-introducing-the-valuedisruptor-5b467730bbe
- jiggawatts 5y agoMark and sweep garbage collection is optimal for some kinds of multi-threaded algorithms. If further minimised with judicious use of value types, it can be surprisingly difficult to outperform it even with carefully tuned C++ or Rust code.
- jayd16 5y agoI assume temp solutions and low hanging fruit was added in the move to .net core and the new compiler. Now that it's more stable, things can get tightened up.