Slight .NET 9 improvement for System.Text.Json

Date Added (UTC):

25 Apr 2024 @ 15:30

Date Updated (UTC):

25 Apr 2024 @ 15:30

.NET Version(s):

.NET 8 .NET 9



Added By:
Profile Image

Bangkok, Thailand    
Senior Software Engineer

Benchmark Results:

Benchmark Code:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Jobs;
using System.Text.Json;

namespace Benchmarks;

[HideColumns(Column.RatioSD, Column.AllocRatio)]
[SimpleJob(RuntimeMoniker.Net80, baseline: true)]
public class SystemTextJsonBenchmark
    private SampleModel model;
    private string json;

    public void GlobalSetup()
        model = new SampleModel();
        json = "{\"Id\":1,\"UniqueId\":\"fcd03978-0562-4166-bad9-55605e9d5ace\",\"Name\":\"test_name\",\"CreatedAt\":\"2024-04-25T09:39:10.6768869Z\"}";

    public void Serialize() => JsonSerializer.Serialize(model);

    public void Deserialize() => JsonSerializer.Deserialize<SampleModel>(json);

public class SampleModel
    public int Id { get; set; } = 1;

    public Guid UniqueId { get; set; } = Guid.NewGuid();

    public string Name { get; set; } = "test_name";

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

// .NET 8
public void Serialize()
// .NET 8
public void Deserialize()

// .NET 8
.method public hidebysig 
    instance void Serialize () cil managed 
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 23 00 00 00 01 5f 00 00
    // Method begins at RVA 0x2068
    // Code size 14 (0xe)
    .maxstack 8

    // sequence point: (line 36, col 32) to (line 36, col 63) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class Benchmarks.SampleModel Benchmarks.SystemTextJsonBenchmark::model
    IL_0006: ldnull
    IL_0007: call string [System.Text.Json]System.Text.Json.JsonSerializer::Serialize<class Benchmarks.SampleModel>(!!0, class [System.Text.Json]System.Text.Json.JsonSerializerOptions)
    IL_000c: pop
    IL_000d: ret
// .NET 8
.method public hidebysig 
    instance void Deserialize () 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 14 (0xe)
    .maxstack 8

    // sequence point: (line 39, col 34) to (line 39, col 79) in _
    IL_0000: ldarg.0
    IL_0001: ldfld string Benchmarks.SystemTextJsonBenchmark::json
    IL_0006: ldnull
    IL_0007: call !!0 [System.Text.Json]System.Text.Json.JsonSerializer::Deserialize<class Benchmarks.SampleModel>(string, class [System.Text.Json]System.Text.Json.JsonSerializerOptions)
    IL_000c: pop
    IL_000d: ret

// .NET 8 (X64)
    L0000: push rsi
    L0001: push rbx
    L0002: sub rsp, 0x48
    L0006: mov rax, [rcx+8]
    L000a: mov [rsp+0x40], rax
    L000f: call qword ptr [0x7fff4cbccf18]
    L0015: mov rbx, rax
    L0018: cmp byte ptr [rbx+0x8d], 0
    L001f: je short L0050
    L0021: mov rcx, 0x7fff52e3d090
    L002b: call 0x00007fffac2d4350
    L0030: mov rcx, 0x22d599a2008
    L003a: cmp rax, rcx
    L003d: je L00dd
    L0043: mov rsi, [rbx+0x10]
    L0047: test rsi, rsi
    L004a: jne short L0065
    L004c: xor ecx, ecx
    L004e: jmp short L006c
    L0050: mov rcx, rbx
    L0053: call qword ptr [0x7fff4cbcd410]
    L0059: jmp short L0021
    L005b: mov r8, rdx
    L005e: test r8, r8
    L0061: je short L00c0
    L0063: jmp short L00b1
    L0065: mov rcx, [rsi+0xb8]
    L006c: cmp rax, rcx
    L006f: je short L00ac
    L0071: mov rdx, rax
    L0074: mov byte ptr [rsp+0x38], 1
    L0079: mov byte ptr [rsp+0x39], 1
    L007e: xor ecx, ecx
    L0080: mov [rsp+0x20], ecx
    L0084: mov [rsp+0x28], ecx
    L0088: mov rcx, rbx
    L008b: movzx r9d, word ptr [rsp+0x38]
    L0091: mov r8d, 1
    L0097: call qword ptr [0x7fff4cbccdf8]
    L009d: mov rsi, rax
    L00a0: lea rcx, [rbx+0x10]
    L00a4: mov rdx, rsi
    L00a7: call 0x00007fffac32a680
    L00ac: mov rdx, rsi
    L00af: jmp short L005b
    L00b1: mov rcx, 0x7fff52e3d4e0
    L00bb: cmp [r8], rcx
    L00be: jne short L00ee
    L00c0: lea rdx, [rsp+0x40]
    L00c5: mov rcx, 0x7fff52e3d6d8
    L00cf: call qword ptr [0x7fff4d84cea0]
    L00d5: nop
    L00d6: add rsp, 0x48
    L00da: pop rbx
    L00db: pop rsi
    L00dc: ret
    L00dd: mov rcx, rbx
    L00e0: call qword ptr [0x7fff4cbcce58]
    L00e6: mov rdx, rax
    L00e9: jmp L005b
    L00ee: call qword ptr [0x7fff4c7843f0]
    L00f4: int3
// .NET 8 (X64)
    L0000: push rdi
    L0001: push rsi
    L0002: push rbx
    L0003: sub rsp, 0x50
    L0007: xor eax, eax
    L0009: mov [rsp+0x38], rax
    L000e: mov rbx, [rcx+0x10]
    L0012: test rbx, rbx
    L0015: je L00fa
    L001b: call qword ptr [0x7fff4cbccf18]
    L0021: mov rsi, rax
    L0024: cmp byte ptr [rsi+0x8d], 0
    L002b: je short L005c
    L002d: mov rcx, 0x7fff52e3d090
    L0037: call 0x00007fffac2d4350
    L003c: mov rcx, 0x22d599a2008
    L0046: cmp rax, rcx
    L0049: je L0118
    L004f: mov rdi, [rsi+0x10]
    L0053: test rdi, rdi
    L0056: jne short L0071
    L0058: xor ecx, ecx
    L005a: jmp short L0078
    L005c: mov rcx, rsi
    L005f: call qword ptr [0x7fff4cbcd410]
    L0065: jmp short L002d
    L0067: mov r8, rdx
    L006a: test r8, r8
    L006d: je short L00cc
    L006f: jmp short L00bd
    L0071: mov rcx, [rdi+0xb8]
    L0078: cmp rax, rcx
    L007b: je short L00b8
    L007d: mov rdx, rax
    L0080: mov byte ptr [rsp+0x48], 1
    L0085: mov byte ptr [rsp+0x49], 1
    L008a: xor ecx, ecx
    L008c: mov [rsp+0x20], ecx
    L0090: mov [rsp+0x28], ecx
    L0094: mov rcx, rsi
    L0097: movzx r9d, word ptr [rsp+0x48]
    L009d: mov r8d, 1
    L00a3: call qword ptr [0x7fff4cbccdf8]
    L00a9: mov rdi, rax
    L00ac: lea rcx, [rsi+0x10]
    L00b0: mov rdx, rdi
    L00b3: call 0x00007fffac32a680
    L00b8: mov rdx, rdi
    L00bb: jmp short L0067
    L00bd: mov rcx, 0x7fff52e3d4e0
    L00c7: cmp [r8], rcx
    L00ca: jne short L0129
    L00cc: lea rdx, [rbx+0xc]
    L00d0: mov ecx, [rbx+8]
    L00d3: mov [rsp+0x38], rdx
    L00d8: mov [rsp+0x40], ecx
    L00dc: lea rdx, [rsp+0x38]
    L00e1: mov rcx, 0x7fff52e3d850
    L00eb: call qword ptr [0x7fff4da74888]
    L00f1: nop
    L00f2: add rsp, 0x50
    L00f6: pop rbx
    L00f7: pop rsi
    L00f8: pop rdi
    L00f9: ret
    L00fa: mov ecx, 0x3174
    L00ff: mov rdx, 0x7fff4cbd86e0
    L0109: call 0x00007fffac327650
    L010e: mov rcx, rax
    L0111: call qword ptr [0x7fff4d7ecc78]
    L0117: int3
    L0118: mov rcx, rsi
    L011b: call qword ptr [0x7fff4cbcce58]
    L0121: mov rdx, rax
    L0124: jmp L0067
    L0129: call qword ptr [0x7fff4c7843f0]
    L012f: int3

Benchmark Description:

There is a very slight improvement for Serialize and Deserialize in .NET 9 for System.Text.Json, Still memory allocation is the same as .NET 8.

The provided benchmark code is designed to measure the performance of the `System.Text.Json` library in .NET for serializing and deserializing objects. The setup includes configurations for running the benchmarks under two different .NET runtime versions, specifically .NET 8.0 and .NET 9.0, allowing for a comparison between these versions regarding the performance of JSON operations. ### General Setup - **.NET Versions:** The benchmarks are set to run under .NET 8.0 (as the baseline) and .NET 9.0. This setup is intended to compare the performance improvements or regressions between these two versions of the .NET runtime. - **Memory Diagnoser:** The `MemoryDiagnoser` attribute is used with `false` to disable it, indicating that memory allocation metrics are not the primary focus of this benchmark. - **Columns:** The `HideColumns` attribute is used to exclude specific columns related to standard deviation ratio and allocation ratio from the results, focusing the output on the primary performance metrics. - **Jobs:** Two `SimpleJob` configurations are defined for running the benchmarks under the specified .NET runtime versions. ### Benchmark Methods #### 1. Serialize Method - **Purpose:** This method measures the performance of serializing a `SampleModel` object to a JSON string using `System.Text.Json.JsonSerializer`. - **Performance Aspect:** It tests how quickly the `JsonSerializer` can convert an object instance into a JSON string representation. This operation is crucial in applications that frequently serialize data to send over networks, store in files, or communicate between different parts of an application. - **Expected Insights:** Running this benchmark will provide insights into the serialization speed and any performance improvements or regressions between .NET 8.0 and .NET 9.0. Faster serialization times are generally desired, especially in high-throughput or latency-sensitive applications. #### 2. Deserialize Method - **Purpose:** This method evaluates the performance of deserializing a JSON string back into a `SampleModel` object. - **Performance Aspect:** It focuses on the efficiency of the `JsonSerializer` in parsing a JSON string and creating an object instance with the corresponding data. Deserialization is a common operation when receiving JSON data from web services, files, or external systems, making its performance critical for many applications. - **Expected Insights:** The benchmark will reveal how quickly the .NET runtime can parse and instantiate objects from JSON strings. Improvements in deserialization times can significantly benefit applications that rely on consuming JSON-formatted data, indicating more efficient data processing capabilities between different .NET versions. ### Conclusion By running these benchmarks, developers can gain valuable insights into the performance characteristics of JSON serialization and deserialization operations in different .NET runtime versions. This information can guide optimization efforts, framework version decisions, and understanding of how .NET's JSON processing capabilities evolve over time. The specific focus on serialization and deserialization performance is important due to the widespread use of JSON for data interchange in modern software development.

Benchmark Comments: