Reflection GetCustomAttributes performance improvement in .NET 8 compared to .NET 7




Date Added (UTC):

15 Apr 2024 @ 18:30

Date Updated (UTC):

15 Apr 2024 @ 18:30


.NET Version(s):

.NET 7 .NET 8

Tag(s):

#.Net8PerfImprovement #Reflection


Added By:
Profile Image

Blog   
Ireland    
.NET Developer and tech lead from Ireland!

Benchmark Results:





Benchmark Code:



using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using System;

[HideColumns("Error", "StdDev", "Median", "RatioSD")]
[MemoryDiagnoser(displayGenColumns: false)]
[Config(typeof(Config))]
public class ReflectionDotnet8
{
    [Benchmark]
    public object[] GetCustomAttributes() => typeof(C).GetCustomAttributes(typeof(MyAttribute), inherit: true);

    [My(Value1 = 1, Value2 = 2)]
    class C { }

    [AttributeUsage(AttributeTargets.All)]
    public class MyAttribute : Attribute
    {
        public int Value1 { get; set; }
        public int Value2 { get; set; }
    }
    private class Config : ManualConfig
    {
        public Config()
        {
            AddJob(Job.Default.WithId(".NET 7").WithRuntime(CoreRuntime.Core70).AsBaseline());
            AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80));

            SummaryStyle =
                SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
        }
    }

}

// .NET 7, .NET 8
public object[] GetCustomAttributes()
{
    return typeof(C).GetCustomAttributes(typeof(MyAttribute), true);
}

// .NET 7
.method public hidebysig 
    instance object[] GetCustomAttributes () cil managed 
{
    .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
        01 00 01 00 00
    )
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 19 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x209d
    // Code size 27 (0x1b)
    .maxstack 8

    // sequence point: (line 26, col 46) to (line 26, col 111) in _
    IL_0000: ldtoken ReflectionDotnet8/C
    IL_0005: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_000a: ldtoken ReflectionDotnet8/MyAttribute
    IL_000f: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_0014: ldc.i4.1
    IL_0015: callvirt instance object[] [System.Runtime]System.Reflection.MemberInfo::GetCustomAttributes(class [System.Runtime]System.Type, bool)
    IL_001a: ret
}

// .NET 8
.method public hidebysig 
    instance object[] GetCustomAttributes () cil managed 
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
        01 00 01 00 00
    )
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 19 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x2050
    // Code size 27 (0x1b)
    .maxstack 8

    // sequence point: (line 26, col 46) to (line 26, col 111) in _
    IL_0000: ldtoken ReflectionDotnet8/C
    IL_0005: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_000a: ldtoken ReflectionDotnet8/MyAttribute
    IL_000f: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_0014: ldc.i4.1
    IL_0015: callvirt instance object[] [System.Runtime]System.Reflection.MemberInfo::GetCustomAttributes(class [System.Runtime]System.Type, bool)
    IL_001a: ret
}

// .NET 7 (X64)
GetCustomAttributes()
    L0000: push rsi
    L0001: sub rsp, 0x20
    L0005: mov rcx, 0x7ffd3abad5c8
    L000f: call 0x00007ffd9473d940
    L0014: mov rsi, rax
    L0017: mov rcx, 0x7ffd3abad768
    L0021: call 0x00007ffd9473d940
    L0026: mov rdx, rax
    L0029: mov rcx, rsi
    L002c: mov r8d, 1
    L0032: add rsp, 0x20
    L0036: pop rsi
    L0037: jmp qword ptr [0x7ffd34bf8a48]
// .NET 8 (X64)
GetCustomAttributes()
    L0000: push rsi
    L0001: push rbx
    L0002: sub rsp, 0x28
    L0006: mov rcx, 0x7ffa90eed110
    L0010: call 0x00007ffaea6e4350
    L0015: mov rbx, rax
    L0018: test rbx, rbx
    L001b: je short L0053
    L001d: mov rcx, 0x7ffa8ab0a318
    L0027: cmp [rbx], rcx
    L002a: jne short L0053
    L002c: mov rcx, 0x7ffa90eecf90
    L0036: call 0x00007ffaea6e4350
    L003b: mov rcx, rax
    L003e: mov rdx, rbx
    L0041: mov r8d, 1
    L0047: add rsp, 0x28
    L004b: pop rbx
    L004c: pop rsi
    L004d: jmp qword ptr [0x7ffa8ad04e40]
    L0053: mov rcx, 0x7ffa8ac567b0
    L005d: call 0x00007ffaea73ae10
    L0062: mov rbx, rax
    L0065: call qword ptr [0x7ffa8af84660]
    L006b: mov rsi, rax
    L006e: mov ecx, 0xe9
    L0073: mov rdx, 0x7ffa8aa74000
    L007d: call 0x00007ffaea737650
    L0082: mov r8, rax
    L0085: mov rdx, rsi
    L0088: mov rcx, rbx
    L008b: call qword ptr [0x7ffa8abcf750]
    L0091: mov rcx, rbx
    L0094: call 0x00007ffaea678b00
    L0099: int3


Benchmark Description:


A small but welcome improvement. [PR](https://github.com/dotnet/runtime/pull/87902)

The provided benchmark code is designed to evaluate the performance of reflection operations in .NET, specifically focusing on the `GetCustomAttributes` method. This benchmark is set up using BenchmarkDotNet, a powerful .NET library that makes it easy to create benchmarks that are accurate and reliable. The configuration and setup details, along with the rationale behind the benchmark method, are explained below. ### General Setup and Configuration - **.NET Versions:** The benchmark is configured to run against two versions of .NET: .NET 7 and .NET 8. This is specified in the `Config` class where two jobs are added, one for each .NET version. This allows for performance comparison across these versions. - **BenchmarkDotNet Attributes:** - `HideColumns`: This attribute hides specific columns in the benchmark output (Error, StdDev, Median, RatioSD) to focus on the most relevant data. - `MemoryDiagnoser`: Enabled with `displayGenColumns` set to false, indicating that memory allocation data will be included in the benchmark results, but without showing the garbage collection generation columns. - `Config`: Specifies a custom configuration for the benchmark, defined in the `Config` class. ### Benchmark Method: `GetCustomAttributes` - **Purpose:** This method is designed to measure the performance of retrieving custom attributes applied to a class using reflection. Reflection is a powerful feature in .NET that allows runtime type inspection and invocation. However, it's known to be relatively slow compared to direct code execution, making its performance critical to understand and optimize in performance-sensitive applications. - **Performance Aspect:** The benchmark specifically tests how quickly the `GetCustomAttributes` method can retrieve attributes of a given type (`MyAttribute`) applied to a class (`C`). This operation is common in scenarios where metadata about classes or members is needed at runtime, such as in custom serialization, ORM mapping, or implementing dependency injection containers. - **Expected Results/Insights:** - **Performance Comparison:** By running this benchmark against .NET 7 and .NET 8, you can observe any performance improvements or regressions in the reflection API between these versions. - **Memory Allocation:** The `MemoryDiagnoser` attribute will provide insights into how much memory is allocated by the `GetCustomAttributes` method. This is important because excessive memory allocation can lead to GC pressure and degraded performance. - **Attribute Retrieval Efficiency:** The results will show how efficiently attributes can be retrieved at runtime, which can inform decisions about using custom attributes in performance-critical paths of an application. ### Conclusion This benchmark is a focused test designed to evaluate the performance of a specific reflection operation (`GetCustomAttributes`) in .NET. By comparing the performance across different .NET versions, developers can gain insights into the efficiency of reflection operations and their impact on application performance. The results can guide optimization efforts, especially in scenarios where reflection is used extensively. Understanding the balance between the flexibility provided by reflection and its performance implications is crucial for developing high-performance .NET applications.


Benchmark Comments: