DateTimeOffset.ParseExact .NET 8 performance improvements




Date Added (UTC):

15 Apr 2024 @ 19:45

Date Updated (UTC):

15 Apr 2024 @ 19:45


.NET Version(s):

.NET 7 .NET 8

Tag(s):

#.Net8PerfImprovement


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.Globalization;
using System;

[HideColumns("Error", "StdDev", "Median", "RatioSD")]
[MemoryDiagnoser(displayGenColumns: false)]
[Config(typeof(Config))]
public class DateTimeParseExact
{
    private const string Format = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'";

    private readonly string _s = new DateTime(1955, 11, 5, 6, 0, 0, DateTimeKind.Utc).ToString(Format, CultureInfo.InvariantCulture);

    [Benchmark]
    public void ParseExact() => DateTimeOffset.ParseExact(_s, Format, CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite | DateTimeStyles.AssumeUniversal);

    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.Percentage);
        }
    }
}

// .NET 7, .NET 8
public void ParseExact()
{
    DateTimeOffset.ParseExact(_s, "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", CultureInfo.InvariantCulture, DateTimeStyles.AllowInnerWhite | DateTimeStyles.AssumeUniversal);
}

// .NET 7
.method public hidebysig 
    instance void ParseExact () 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 0x208e
    // Code size 25 (0x19)
    .maxstack 8

    // sequence point: (line 31, col 33) to (line 31, col 165) in _
    IL_0000: ldarg.0
    IL_0001: ldfld string DateTimeParseExact::_s
    IL_0006: ldstr "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"
    IL_000b: call class [System.Runtime]System.Globalization.CultureInfo [System.Runtime]System.Globalization.CultureInfo::get_InvariantCulture()
    IL_0010: ldc.i4.s 68
    IL_0012: call valuetype [System.Runtime]System.DateTimeOffset [System.Runtime]System.DateTimeOffset::ParseExact(string, string, class [System.Runtime]System.IFormatProvider, valuetype [System.Runtime]System.Globalization.DateTimeStyles)
    IL_0017: pop
    IL_0018: ret
}

// .NET 8
.method public hidebysig 
    instance void ParseExact () 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 0x2050
    // Code size 25 (0x19)
    .maxstack 8

    // sequence point: (line 31, col 33) to (line 31, col 165) in _
    IL_0000: ldarg.0
    IL_0001: ldfld string DateTimeParseExact::_s
    IL_0006: ldstr "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"
    IL_000b: call class [System.Runtime]System.Globalization.CultureInfo [System.Runtime]System.Globalization.CultureInfo::get_InvariantCulture()
    IL_0010: ldc.i4.s 68
    IL_0012: call valuetype [System.Runtime]System.DateTimeOffset [System.Runtime]System.DateTimeOffset::ParseExact(string, string, class [System.Runtime]System.IFormatProvider, valuetype [System.Runtime]System.Globalization.DateTimeStyles)
    IL_0017: pop
    IL_0018: ret
}

// .NET 7 (X64)
ParseExact()
    L0000: push rdi
    L0001: push rsi
    L0002: push rbx
    L0003: sub rsp, 0x60
    L0007: vzeroupper
    L000a: xor eax, eax
    L000c: mov [rsp+0x28], rax
    L0011: vxorps xmm4, xmm4, xmm4
    L0015: vmovdqa [rsp+0x30], xmm4
    L001b: mov [rsp+0x40], rax
    L0020: mov rsi, [rcx+8]
    L0024: mov rdx, 0x1c55ec00528
    L002e: mov rdi, [rdx]
    L0031: mov rdx, 0x1c55ec91ea8
    L003b: mov rdx, [rdx]
    L003e: mov ecx, 0x44
    L0043: call qword ptr [0x7ffa2c155ae0]
    L0049: mov ebx, eax
    L004b: test rsi, rsi
    L004e: je L00f4
    L0054: lea rcx, [rsi+0xc]
    L0058: mov eax, [rsi+8]
    L005b: mov rdx, 0x1c55ec91ea0
    L0065: mov rdx, [rdx]
    L0068: add rdx, 0xc
    L006c: mov [rsp+0x38], rcx
    L0071: mov [rsp+0x40], eax
    L0075: mov [rsp+0x28], rdx
    L007a: mov dword ptr [rsp+0x30], 0x23
    L0082: mov rcx, rdi
    L0085: call qword ptr [0x7ffa2c60d8d0]
    L008b: mov r8, rax
    L008e: lea rdx, [rsp+0x58]
    L0093: mov [rsp+0x20], rdx
    L0098: lea rdx, [rsp+0x28]
    L009d: lea rcx, [rsp+0x38]
    L00a2: mov r9d, ebx
    L00a5: call qword ptr [0x7ffa325bf1b0]
    L00ab: vxorps xmm0, xmm0, xmm0
    L00af: vmovupd [rsp+0x48], xmm0
    L00b5: mov rsi, 0x3fffffffffffffff
    L00bf: and rsi, rax
    L00c2: mov rdi, [rsp+0x58]
    L00c7: mov rcx, rdi
    L00ca: call qword ptr [0x7ffa2c155ab0]
    L00d0: mov rcx, 0x2bca2875f4373fff
    L00da: cmp rsi, rcx
    L00dd: ja short L0100
    L00df: mov rcx, rsi
    L00e2: mov rdx, rdi
    L00e5: call qword ptr [0x7ffa2c155ac8]
    L00eb: nop
    L00ec: add rsp, 0x60
    L00f0: pop rbx
    L00f1: pop rsi
    L00f2: pop rdi
    L00f3: ret
    L00f4: mov ecx, 0x12
    L00f9: call qword ptr [0x7ffa2c52f528]
    L00ff: int3
    L0100: call qword ptr [0x7ffa2c0db6d8]
    L0106: int3
// .NET 8 (X64)
ParseExact()
    L0000: push rdi
    L0001: push rsi
    L0002: push rbx
    L0003: sub rsp, 0x50
    L0007: xor eax, eax
    L0009: mov [rsp+0x28], rax
    L000e: vxorps xmm4, xmm4, xmm4
    L0012: vmovdqa [rsp+0x30], xmm4
    L0018: mov [rsp+0x40], rax
    L001d: mov rbx, [rcx+8]
    L0021: mov rcx, 0x1bf05000428
    L002b: mov rsi, [rcx]
    L002e: mov ecx, 0x44
    L0033: mov rdx, 0x1c0e94a8618
    L003d: call qword ptr [0x7ff9db434990]
    L0043: mov edi, eax
    L0045: test rbx, rbx
    L0048: je L00e4
    L004e: lea rcx, [rbx+0xc]
    L0052: mov eax, [rbx+8]
    L0055: mov rdx, 0x1bf0505fcc0
    L005f: mov rdx, [rdx]
    L0062: add rdx, 0xc
    L0066: mov [rsp+0x38], rcx
    L006b: mov [rsp+0x40], eax
    L006f: mov [rsp+0x28], rdx
    L0074: mov dword ptr [rsp+0x30], 0x23
    L007c: mov rcx, rsi
    L007f: call qword ptr [0x7ff9dbbccfa8]
    L0085: mov r8, rax
    L0088: lea rdx, [rsp+0x48]
    L008d: mov [rsp+0x20], rdx
    L0092: lea rdx, [rsp+0x28]
    L0097: lea rcx, [rsp+0x38]
    L009c: mov r9d, edi
    L009f: call qword ptr [0x7ff9e1bd6cb8]
    L00a5: mov rbx, 0x3fffffffffffffff
    L00af: and rbx, rax
    L00b2: mov rsi, [rsp+0x48]
    L00b7: mov rcx, rsi
    L00ba: call qword ptr [0x7ff9db434960]
    L00c0: mov rcx, 0x2bca2875f4373fff
    L00ca: cmp rbx, rcx
    L00cd: ja short L00f0
    L00cf: mov rcx, rbx
    L00d2: mov rdx, rsi
    L00d5: call qword ptr [0x7ff9db434978]
    L00db: nop
    L00dc: add rsp, 0x50
    L00e0: pop rbx
    L00e1: pop rsi
    L00e2: pop rdi
    L00e3: ret
    L00e4: mov ecx, 0x12
    L00e9: call qword ptr [0x7ff9db5ce7a8]
    L00ef: int3
    L00f0: call qword ptr [0x7ff9db3b5710]
    L00f6: int3


Benchmark Description:


### General Setup Overview The benchmark setup provided is designed to measure the performance of parsing a date-time string into a `DateTimeOffset` object using the `ParseExact` method in different .NET runtime versions. The benchmark is configured using BenchmarkDotNet, a powerful library for benchmarking .NET code. Here's a breakdown of the general setup: - **.NET Versions:** The benchmark is specifically comparing performance across two versions of the .NET runtime: .NET 7 and .NET 8. This is achieved by configuring two separate jobs, one for each runtime version, with .NET 7 set as the baseline for comparison. - **Configuration:** The benchmark class is annotated with attributes that control the output and behavior of the benchmark. `HideColumns` hides specific columns in the output report for clarity, while `MemoryDiagnoser` is configured to not display garbage collection generation columns. - **Custom Config:** A custom configuration class (`Config`) is defined within the benchmark class, specifying the jobs (runtime versions) to run and customizing the summary style to display ratio differences in percentage format. ### Benchmark Method: `ParseExact` #### Purpose and Performance Aspect The `ParseExact` method benchmark is designed to measure the performance of parsing a specific date-time string format into a `DateTimeOffset` object with specific parsing options. This operation is common in applications that need to interpret date-time data from text sources (like logs, external APIs, etc.) with known formats. #### What It Measures The benchmark measures: - **Throughput:** How quickly the .NET runtime can parse the date-time string using the `ParseExact` method. - **Allocation:** Although the `MemoryDiagnoser` is configured not to display garbage collection generation columns, it's still tracking the memory allocated by the operation. This can provide insights into the efficiency of the parsing operation in terms of memory usage. #### Importance Parsing date-time strings efficiently is crucial for performance-sensitive applications, especially those dealing with large volumes of data or requiring real-time processing. Poor performance or excessive memory allocation in these operations can lead to bottlenecks. #### Expected Results or Insights - **Performance Comparison:** By running this benchmark across different .NET versions, you can identify performance improvements or regressions in the `DateTimeOffset.ParseExact` method between .NET 7 and .NET 8. - **Efficiency Insights:** The benchmark can reveal how memory-efficient the parsing operation is across the compared .NET versions. Even if throughput remains similar, improvements in memory allocation can lead to reduced garbage collection pressure and better overall performance. In summary, this benchmark method provides valuable insights into the performance and efficiency of date-time parsing operations across different versions of the .NET runtime, helping developers make informed decisions about upgrading or optimizing their applications.


Benchmark Comments: