Exception handling performance improvements in .NET 9 (Preview 3+) versus .NET 8
Date Added (UTC):
13 Apr 2024 @ 00:33
Date Updated (UTC):13 Apr 2024 @ 00:33
.NET Version(s): Tag(s):
Added By:
.NET Developer and tech lead from Ireland!
Benchmark Results:
Benchmark Code:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Environments;
namespace ExceptionBenchmark
{
[Config(typeof(Config))]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio, Column.Gen0, Column.Gen1)]
[MemoryDiagnoser]
public class ExceptionBenchmark
{
private const int NumberOfIterations = 1000;
[Benchmark]
public void ThrowAndCatchException()
{
for (int i = 0; i < NumberOfIterations; i++)
{
try
{
ThrowException();
}
catch
{
// Exception caught - the cost of this is what we're measuring
}
}
}
private void ThrowException()
{
throw new System.Exception("This is a test exception.");
}
private class Config : ManualConfig
{
public Config()
{
AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80).AsBaseline());
AddJob(Job.Default.WithId(".NET 9").WithRuntime(CoreRuntime.Core90));
SummaryStyle =
SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
}
}
}
}
Powered by SharpLab
// .NET 8
public void ThrowAndCatchException()
{
int num = 0;
while (num < 1000)
{
try
{
ThrowException();
}
catch
{
}
num++;
}
}
Powered by SharpLab
// .NET 8
.method public hidebysig
instance void ThrowAndCatchException () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 1c 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2050
// Code size 29 (0x1d)
.maxstack 2
.locals init (
[0] int32 i
)
// sequence point: (line 31, col 18) to (line 31, col 27) in _
IL_0000: ldc.i4.0
IL_0001: stloc.0
// sequence point: hidden
IL_0002: br.s IL_0014
// loop start (head: IL_0014)
// sequence point: hidden
IL_0004: nop
.try
{
// sequence point: (line 35, col 21) to (line 35, col 38) in _
IL_0005: ldarg.0
IL_0006: call instance void ExceptionBenchmark.ExceptionBenchmark::ThrowException()
// sequence point: (line 36, col 17) to (line 36, col 18) in _
IL_000b: leave.s IL_0010
}
Powered by SharpLab
|
Benchmark Description:
PR -> https://github.com/dotnet/runtime/pull/88034
Looks like MS have ***ported NativeAOT exception handling to CoreCLR***.
GH issue thread looking at how performance would be tested ->
https://github.com/dotnet/runtime/issues/77568#issuecomment-1453934050
The provided benchmark setup is designed to measure the performance implications of throwing and catching exceptions in .NET applications. It uses BenchmarkDotNet, a powerful .NET library for benchmarking, to conduct and report on these measurements. Let's break down the setup and the rationale behind the benchmark method.
### General Setup
- **.NET Version:** The benchmark configuration specifies two .NET versions, ".NET 8" and ".NET 9", using the `CoreRuntime.Core80` and `CoreRuntime.Core90` identifiers. This suggests that the benchmark aims to compare the performance of exception handling between these two future versions of .NET, assuming the code snippet is forward-looking as of my last update in 2023.
- **Configuration:** The `Config` class extends `ManualConfig` from BenchmarkDotNet, setting up two jobs for the two .NET versions and marking the ".NET 8" job as the baseline. This means results from ".NET 9" will be compared against ".NET 8" to see if there are any performance improvements or regressions.
- **Memory Diagnoser:** The `[MemoryDiagnoser]` attribute is used to collect and report memory allocation statistics, which is crucial for understanding the memory overhead associated with exception handling.
- **Columns:** The benchmark hides several columns (Job, RatioSD, AllocRatio, Gen0, Gen1) to focus on the most relevant metrics for this test.
### Benchmark Method: `ThrowAndCatchException`
- **Purpose:** This method is designed to measure the performance cost of throwing and catching exceptions in a tight loop. It does this by repeatedly throwing an exception inside a try block and catching it immediately in a catch block, for a total of `NumberOfIterations` times (1000 in this case).
- **Performance Aspect:** The benchmark specifically tests the overhead associated with the exception throwing and catching mechanism in .NET. This includes the time it takes to create, throw, and catch an exception, as well as any additional memory allocations that occur as a result.
- **Why It's Important:** Exception handling is a common pattern in software development, but it's known to be relatively expensive in terms of performance. Understanding the cost of exceptions can help developers make informed decisions about when and how to use them. For example, in performance-critical paths, developers might opt to use error codes or other mechanisms instead of exceptions.
- **Expected Results/Insights:** From running this benchmark, one would expect to gain insights into how exception handling performance might have changed between ".NET 8" and ".NET 9". If the performance improves in ".NET 9", it could indicate optimizations in the runtime's exception handling mechanism. Conversely, if performance regresses, it might warrant further investigation or consideration from developers relying heavily on exceptions. Additionally, the memory diagnostics can reveal if there's a significant difference in memory allocation behavior between the two versions, which could be just as crucial for applications where memory efficiency is a priority.
In summary, this benchmark setup and method provide a focused way to assess the performance implications of exception handling across different versions of the .NET runtime, offering valuable insights for developers looking to optimize their applications.