5 ms·
It’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
by Semaphor 5y ago
It’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.