A few highlights, as can be seen in some of the blog posts mentioned by other replies:
- 'Span<T>' to represent chunks of either managed OR unmanaged memory without using unsafe pointers throughout [0][1]
- Not relevant to this task necessarily, but a lot of machinery has been added to allow reuse of objects for tasks like queuing thread pool work, or waiting for an asynchronous result.
- Lots of intrinsics helpers for SIMD workloads, and increased usage of such intrinsics in internal parsers/etc.
- Generally improving a lot of the internal IO to take advantage of other improvements in the runtime.
- PGO (Performance Guided Optimization) on the JIT side, essentially helps with things like better devirt [2] and other improvements.
- AOT compilation, if that's your thing, (I do believe the fastest C# 1BRC submissions use this)
[0] - To be clear, unsafe can still be faster, however for most cases Span is fine and gives you a little more runtime safety.
[1] - You can also grab a Span<T> of a primitive (i.e. int, char) within a method, so long as you don't blow up stack, this is very nice when you need a small buffer for parsing but don't want to thrash the GC or deal with volatile or locks on some sort of pool.
[2] - Devirt historically was a problem in 'call heavy' .NET apps when Interfaces are used, before PGO there was more than one library I worked on where we intentionally used abstract base classes rather than interfaces due to the need to squeeze as much out as we could.
.NET team has been doubling down on performance improvements, people forget CLR also has features to support C like languages (hence Managed C++ and C++/CLI), and many of those capabilities are now surfaced into C# as well.
junto|2 years ago
to11mtm|2 years ago
A few highlights, as can be seen in some of the blog posts mentioned by other replies:
- 'Span<T>' to represent chunks of either managed OR unmanaged memory without using unsafe pointers throughout [0][1]
- Not relevant to this task necessarily, but a lot of machinery has been added to allow reuse of objects for tasks like queuing thread pool work, or waiting for an asynchronous result.
- Lots of intrinsics helpers for SIMD workloads, and increased usage of such intrinsics in internal parsers/etc.
- Generally improving a lot of the internal IO to take advantage of other improvements in the runtime.
- PGO (Performance Guided Optimization) on the JIT side, essentially helps with things like better devirt [2] and other improvements.
- AOT compilation, if that's your thing, (I do believe the fastest C# 1BRC submissions use this)
[0] - To be clear, unsafe can still be faster, however for most cases Span is fine and gives you a little more runtime safety.
[1] - You can also grab a Span<T> of a primitive (i.e. int, char) within a method, so long as you don't blow up stack, this is very nice when you need a small buffer for parsing but don't want to thrash the GC or deal with volatile or locks on some sort of pool.
[2] - Devirt historically was a problem in 'call heavy' .NET apps when Interfaces are used, before PGO there was more than one library I worked on where we intentionally used abstract base classes rather than interfaces due to the need to squeeze as much out as we could.
Anon4Now|2 years ago
[1] https://devblogs.microsoft.com/dotnet/performance-improvemen...
pjmlp|2 years ago
.NET team has been doubling down on performance improvements, people forget CLR also has features to support C like languages (hence Managed C++ and C++/CLI), and many of those capabilities are now surfaced into C# as well.