1


Object Creation Performance Analysis




Date Added (UTC):

24 Apr 2024 @ 09:22

Date Updated (UTC):

24 Apr 2024 @ 09:22


.NET Version(s):

.NET 8

Tag(s):


Added By:
Profile Image

Blog   
Wilmington, DE 19808, USA    
A dedicated executive technical architect who is focused on expanding organizations technology capabilities.

Benchmark Results:





Benchmark Code:



using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using ObjectCreationBenchmarks;

namespace ObjectCreationBenchmarks;

public sealed class Person
{
}

[MemoryDiagnoser]
public class Benchmarks
{
    [Benchmark(Baseline = true)]
    public Person NewKeyword() 
        => new Person();

    [Benchmark]
    public object ActivatorCreateInstanceWithTypeOf() 
        => Activator.CreateInstance(typeof(Person));

    [Benchmark]
    public Person ActivatorCreateInstanceGeneric() 
        => Activator.CreateInstance<Person>();

    [Benchmark]
    public object GetUninitializedObject() 
        => RuntimeHelpers.GetUninitializedObject(typeof(Person));
}

public class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<Benchmarks>();
    }
}

// .NET 8
public Person NewKeyword()
{
    return new Person();
}
// .NET 8
public object ActivatorCreateInstanceWithTypeOf()
{
    return Activator.CreateInstance(typeof(Person));
}
// .NET 8
public Person ActivatorCreateInstanceGeneric()
{
    return Activator.CreateInstance<Person>();
}
// .NET 8
public object GetUninitializedObject()
{
    return RuntimeHelpers.GetUninitializedObject(typeof(Person));
}

// .NET 8
.method public hidebysig 
    instance class ObjectCreationBenchmarks.Person NewKeyword () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 1a 00 00 00 01 5f 01 00 54 02 08 42 61 73
        65 6c 69 6e 65 01
    )
    // Method begins at RVA 0x2058
    // Code size 6 (0x6)
    .maxstack 8

    // sequence point: (line 28, col 12) to (line 28, col 24) in _
    IL_0000: newobj instance void ObjectCreationBenchmarks.Person::.ctor()
    IL_0005: ret
}
// .NET 8
.method public hidebysig 
    instance object ActivatorCreateInstanceWithTypeOf () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 1e 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x205f
    // Code size 16 (0x10)
    .maxstack 8

    // sequence point: (line 32, col 12) to (line 32, col 52) in _
    IL_0000: ldtoken ObjectCreationBenchmarks.Person
    IL_0005: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_000a: call object [System.Runtime]System.Activator::CreateInstance(class [System.Runtime]System.Type)
    IL_000f: ret
}
// .NET 8
.method public hidebysig 
    instance class ObjectCreationBenchmarks.Person ActivatorCreateInstanceGeneric () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 22 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x2070
    // Code size 6 (0x6)
    .maxstack 8

    // sequence point: (line 36, col 12) to (line 36, col 46) in _
    IL_0000: call !!0 [System.Runtime]System.Activator::CreateInstance<class ObjectCreationBenchmarks.Person>()
    IL_0005: ret
}
// .NET 8
.method public hidebysig 
    instance object GetUninitializedObject () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 26 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x2077
    // Code size 16 (0x10)
    .maxstack 8

    // sequence point: (line 40, col 12) to (line 40, col 65) in _
    IL_0000: ldtoken ObjectCreationBenchmarks.Person
    IL_0005: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
    IL_000a: call object [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::GetUninitializedObject(class [System.Runtime]System.Type)
    IL_000f: ret
}

// .NET 8 (X64)
NewKeyword()
    L0000: sub rsp, 0x28
    L0004: mov rcx, 0x7ffd8b2ccd90
    L000e: call 0x00007ffde4afae10
    L0013: nop
    L0014: add rsp, 0x28
    L0018: ret
// .NET 8 (X64)
ActivatorCreateInstanceWithTypeOf()
    L0000: push rsi
    L0001: push rbx
    L0002: sub rsp, 0x28
    L0006: mov rcx, 0x7ffd8b2ccd90
    L0010: call 0x00007ffde4aa4350
    L0015: mov rcx, rax
    L0018: test rcx, rcx
    L001b: je short L0045
    L001d: mov rdx, 0x7ffd84eaa318
    L0027: cmp [rcx], rdx
    L002a: jne short L0045
    L002c: mov edx, 1
    L0031: mov r8d, 1
    L0037: cmp [rcx], ecx
    L0039: add rsp, 0x28
    L003d: pop rbx
    L003e: pop rsi
    L003f: jmp qword ptr [0x7ffd84eb6e08]
    L0045: mov rcx, 0x7ffd84ff67b0
    L004f: call 0x00007ffde4afae10
    L0054: mov rbx, rax
    L0057: call qword ptr [0x7ffd85324660]
    L005d: mov rsi, rax
    L0060: mov ecx, 0x2b3
    L0065: mov rdx, 0x7ffd84e14000
    L006f: call 0x00007ffde4af7650
    L0074: mov r8, rax
    L0077: mov rdx, rsi
    L007a: mov rcx, rbx
    L007d: call qword ptr [0x7ffd84f6f750]
    L0083: mov rcx, rbx
    L0086: call 0x00007ffde4a38b00
    L008b: int3
// .NET 8 (X64)
ActivatorCreateInstanceGeneric()
    L0000: mov rcx, 0x7ffd8b2cd4a8
    L000a: jmp qword ptr [0x7ffd85086130]
// .NET 8 (X64)
GetUninitializedObject()
    L0000: sub rsp, 0x28
    L0004: mov rcx, 0x7ffd8b2ccd90
    L000e: call 0x00007ffde4aa4350
    L0013: mov rcx, rax
    L0016: add rsp, 0x28
    L001a: jmp qword ptr [0x7ffd84f64b40]


Benchmark Description:


In this analysis, we delve into the performance of different methods for creating objects in .NET, utilizing the benchmarking tool BenchmarkDotNet. Our goal is to understand how each method impacts performance and when to use each technique. ##### Environment Setup Runtime: .NET 8.0.3 (8.0.324.11423), X64 RyuJIT AVX2 Garbage Collector: Concurrent Workstation System: Windows 11, AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores .NET SDK: 8.0.202

The provided benchmark code is designed to compare different methods of creating instances of an object in .NET. It uses the BenchmarkDotNet library, a powerful tool for benchmarking .NET code, to measure and compare the performance of these methods. The benchmarks are focused on the `Person` class, which is an empty class in this context. The .NET version isn't explicitly mentioned, but given the methods used, it should be compatible with .NET Core 2.0 and above, including .NET 5 and .NET 6. ### General Setup - **BenchmarkDotNet Attributes**: The `[MemoryDiagnoser]` attribute is used to enable memory diagnostics, allowing the benchmark to report on allocations. - **.NET Version**: Not specified, but the methods used are available in .NET Core 2.0 and later versions. - **Configuration**: The `BenchmarkRunner.Run<Benchmarks>()` in the `Main` method is the entry point for running the benchmarks. BenchmarkDotNet will handle the execution and measurement. ### Benchmark Methods #### 1. `NewKeyword()` - **Purpose**: This method benchmarks the direct instantiation of an object using the `new` keyword. - **Performance Aspect**: It measures the speed and memory allocations involved in the most common way of creating an object instance. - **Importance**: This serves as the baseline for comparison because it's the standard and most straightforward way to create an object in .NET. - **Expected Results**: This method is expected to be the fastest and least memory-intensive way to create an object, serving as a baseline for other methods. #### 2. `ActivatorCreateInstanceWithTypeOf()` - **Purpose**: This method tests the performance of creating an instance using `Activator.CreateInstance` with a `Type` parameter. - **Performance Aspect**: It measures how reflection-based creation compares to the direct `new` keyword in terms of speed and memory allocations. - **Importance**: Reflection is powerful but known to be slower than direct instantiation. Understanding the performance impact is crucial for scenarios where dynamic type creation is necessary. - **Expected Results**: This method is likely to be slower and more memory-intensive than using the `new` keyword, reflecting the overhead of reflection. #### 3. `ActivatorCreateInstanceGeneric()` - **Purpose**: This benchmarks the generic version of `Activator.CreateInstance`, which avoids using the `Type` parameter. - **Performance Aspect**: It evaluates the performance differences between generic reflection and non-generic reflection or direct instantiation. - **Importance**: The generic method could potentially offer performance benefits over its non-generic counterpart by avoiding some runtime type checks. - **Expected Results**: This method might perform better than `Activator.CreateInstance` with a `Type` parameter but still not as good as the direct `new` keyword due to the overhead of reflection. #### 4. `GetUninitializedObject()` - **Purpose**: This method benchmarks the creation of an uninitialized object using `RuntimeHelpers.GetUninitializedObject`. - **Performance Aspect**: It measures the performance of creating an object without calling its constructor. - **Importance**: This approach is rarely used but can be important in serialization, deserialization, and scenarios requiring a high level of control over object instantiation. - **Expected Results**: This method might be faster than reflection-based methods but can lead to objects in an invalid state if not used carefully. It's a specialized case and might show lower memory allocations since constructors are not called. ### Summary Running these benchmarks provides insights into the trade-offs between different object instantiation methods in .NET. The direct `new` keyword is expected to be the most efficient, serving as a baseline. Reflection-based methods (`Activator.CreateInstance`) offer flexibility at the cost of performance. `GetUninitializedObject` is a specialized tool that trades constructor guarantees for potentially faster instantiation. Understanding these trade-offs is crucial for optimizing performance-critical paths in .NET applications.


Benchmark Comments: