JsonSerializer.Serialize/SerializeAsync performance improvements in .NET 8 v .NET 7




Date Added (UTC):

07 Apr 2024 @ 21:56

Date Updated (UTC):

12 Apr 2024 @ 02:14


.NET Version(s):

.NET 7 .NET 8

Tag(s):

#(De)Serialization #.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.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

[Config(typeof(Config))]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)]
[MemoryDiagnoser(displayGenColumns: false)] 
public partial class SerializationBenchmarks
{
    private readonly Rectangle _data = new()
    {
        X = 1,
        Y = 2,
        Width = 3,
        Height = 4,
        Color = new Color { R = 5, G = 6, B = 7, A = 8 }
    };

    [Benchmark]
    public void Serialize() => JsonSerializer.Serialize(Stream.Null, _data, JsonContext.Default.Rectangle);

    [Benchmark]
    public Task SerializeAsync() => JsonSerializer.SerializeAsync(Stream.Null, _data, JsonContext.Default.Rectangle);

    public class Rectangle
    {
        public int X, Y, Width, Height;
        public Color Color;
    }

    public struct Color
    {
        public byte R, G, B, A;
    }

    [JsonSerializable(typeof(Rectangle))]
    [JsonSourceGenerationOptions(IncludeFields = true)]
    private partial class JsonContext : JsonSerializerContext { }

    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 Lowered C# Code unavailable due to errors:
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'
error CS0117: 'SerializationBenchmarks.JsonContext' does not contain a definition for 'Default'
error CS7036: There is no argument given that corresponds to the required parameter 'options' of 'JsonSerializerContext.JsonSerializerContext(JsonSerializerOptions?)'
// .NET 8 Lowered C# Code unavailable due to errors:
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'
error CS0117: 'SerializationBenchmarks.JsonContext' does not contain a definition for 'Default'
error CS7036: There is no argument given that corresponds to the required parameter 'options' of 'JsonSerializerContext.JsonSerializerContext(JsonSerializerOptions?)'

// .NET 7 IL Code unavailable due to errors:
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'
error CS0117: 'SerializationBenchmarks.JsonContext' does not contain a definition for 'Default'
error CS7036: There is no argument given that corresponds to the required parameter 'options' of 'JsonSerializerContext.JsonSerializerContext(JsonSerializerOptions?)'
// .NET 8 IL Code unavailable due to errors:
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'
error CS0117: 'SerializationBenchmarks.JsonContext' does not contain a definition for 'Default'
error CS7036: There is no argument given that corresponds to the required parameter 'options' of 'JsonSerializerContext.JsonSerializerContext(JsonSerializerOptions?)'

// .NET 7 Jit Asm Code unavailable due to errors:
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'
error CS0117: 'SerializationBenchmarks.JsonContext' does not contain a definition for 'Default'
error CS7036: There is no argument given that corresponds to the required parameter 'options' of 'JsonSerializerContext.JsonSerializerContext(JsonSerializerOptions?)'
// .NET 8 Jit Asm Code unavailable due to errors:
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GeneratedSerializerOptions.get'
error CS0534: 'SerializationBenchmarks.JsonContext' does not implement inherited abstract member 'JsonSerializerContext.GetTypeInfo(Type)'
error CS0117: 'SerializationBenchmarks.JsonContext' does not contain a definition for 'Default'
error CS7036: There is no argument given that corresponds to the required parameter 'options' of 'JsonSerializerContext.JsonSerializerContext(JsonSerializerOptions?)'


Benchmark Description:


[PR](https://github.com/dotnet/runtime/pull/78646)

The provided benchmark code is designed to measure the performance of JSON serialization operations using the `System.Text.Json` namespace in .NET. It specifically focuses on the serialization of a custom data structure (`Rectangle` containing a nested structure `Color`) to JSON format. The benchmarks are set up to run on different .NET runtime versions to compare performance across these versions. Let's break down the setup and the rationale behind each benchmark method. ### General Setup - **.NET Versions**: The benchmarks are configured to run on .NET 7 and .NET 8, allowing for performance comparison across these two versions. This is achieved through the `Config` class, which specifies the runtime environments. - **BenchmarkDotNet**: This is a powerful library for benchmarking .NET code, providing detailed insights into the performance characteristics of the code being tested. - **Configuration**: The `Config` class customizes the benchmarking process, including the runtime versions and summary styles. It hides certain columns (Job, RatioSD, AllocRatio) for clarity and disables the display of garbage collection generation columns. - **MemoryDiagnoser**: Enabled with `displayGenColumns: false` to focus on memory allocation metrics without showing garbage collection generation details. ### Benchmark Methods #### 1. `Serialize` - **Purpose**: This method measures the performance of synchronously serializing the `_data` object (an instance of `Rectangle`) to JSON format. - **Performance Aspect**: It tests the CPU and memory performance involved in the serialization process. This includes the efficiency of converting the object graph into a JSON string and writing it to a `Stream.Null`, which effectively discards the output. - **Importance**: Synchronous serialization is a common operation in many applications, especially those that do not support or require asynchronous processing. Understanding its performance characteristics helps in optimizing applications that rely on JSON for data interchange. - **Expected Results**: Insights into the time taken and memory allocated for the serialization process. Comparing results across .NET versions can show improvements or regressions in the framework's serialization performance. #### 2. `SerializeAsync` - **Purpose**: This method measures the performance of asynchronously serializing the `_data` object to JSON format. - **Performance Aspect**: Similar to `Serialize`, but focuses on the asynchronous execution model. It tests the efficiency of the async serialization process, including the overhead of task management and the potential for non-blocking I/O operations. - **Importance**: Asynchronous operations are crucial for performance in I/O-bound scenarios, such as web applications, where they help in achieving scalability and responsiveness. Benchmarking async serialization is important for applications that heavily rely on non-blocking data processing. - **Expected Results**: Insights into the time taken and memory allocated for the async serialization process, along with the overhead introduced by asynchronous operations. Comparing .NET versions can reveal improvements in the async programming model and JSON serialization optimizations. ### Conclusion Running these benchmarks will provide valuable insights into the performance of JSON serialization operations in .NET, both synchronously and asynchronously. By comparing the results across different .NET versions, developers can understand the impact of runtime and library optimizations on their applications' performance. This information is crucial for making informed decisions about upgrading projects to newer .NET versions or optimizing current serialization practices.


Benchmark Comments: