set DOTNET_ReadyToRun=0
set DOTNET_TieredPGO=1
set DOTNET_TC_QuickJitForLoops=1
Here are my own benchmarks from a CPU intensive application without any IO and already optimized for allocations. Application runs a task graph either serially or in parallel. .NET 5
--------------------------
| Method | Mean |
|------------ |---------:|
| RunParallel | 473.4 us |
| Run | 513.5 us |
.NET 6
--------------------------
| Method | Mean |
|------------ |---------:|
| RunParallel | 452.5 us |
| Run | 499.8 us |
.NET 6 PGO
--------------------------
| Method | Mean |
|------------ |---------:|
| RunParallel | 381.8 us |
| Run | 412.2 us |
.NET 5 - .NET 6 -> ~5%
.NET 5 - .NET 6 PGO -> ~20%
Here is what I learned from micro-optimizing a .NET application:- Use BenchmarkDotNet[0] for general measurements and Visual Studio profiler tools for detailed inspection. They help a lot.
- Memory allocations matter. Using capturing lambdas, LINQ, even foreach on interfaces introduce allocations and slows down the application. You can use ClrHeapAllocationAnalyzer[1] to find these hidden allocations.
- Using abstractions with interfaces and casting back to concrete types cause some overhead, though PGO will probably eliminate most of these.
- Use LINQ cautiously as its variants are mostly slower than explicit coding. E.g. .Any() vs .Count == 0
- Checking Logger.IsEnabled() before calling Logger.Debug() etc. helps a lot. You can even automate this with Fody [2], but it breaks Edit&Continue and possibly .NET Hot Reload too, so it may hinder your productivity.
[0] https://github.com/dotnet/BenchmarkDotNet
[1] https://github.com/microsoft/RoslynClrHeapAllocationAnalyzer