Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider fast-path deserialization logic in JSON source generator #55043

Open
Tracked by #77019
layomia opened this issue Jul 1, 2021 · 5 comments
Open
Tracked by #77019

Consider fast-path deserialization logic in JSON source generator #55043

layomia opened this issue Jul 1, 2021 · 5 comments
Labels
area-System.Text.Json source-generator Indicates an issue with a source generator feature tenet-performance Performance related issue
Milestone

Comments

@layomia
Copy link
Contributor

layomia commented Jul 1, 2021

In #51945, we addressed a mode in the JSON source generator that generates optimized serialization logic using Utf8JsonWriter directly. We should consider a similar mode for deserialization using Utf8JsonReader directly.

@layomia layomia added area-System.Text.Json tenet-performance Performance related issue labels Jul 1, 2021
@layomia layomia added this to the 7.0.0 milestone Jul 1, 2021
@layomia layomia self-assigned this Jul 1, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Jul 1, 2021
@ghost
Copy link

ghost commented Jul 1, 2021

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

In #51945, we addressed a mode in the JSON source generator that generates optimized serialization logic using Utf8JsonWriter directly. We should consider a similar mode for deserialization using Utf8JsonReader directly.

Author: layomia
Assignees: layomia
Labels:

area-System.Text.Json, tenet-performance

Milestone: 7.0.0

@layomia
Copy link
Contributor Author

layomia commented Aug 10, 2021

From @mrange in #57117:

Hi.
I was asked if I had any further feedback on the JSON Serializer in ticket: #56995

One thing that came to mind is that in my experiments generating a deserialize method can lead to performance improvements.

I suspect you considered it and for some reason not implemented it but in case you are interested my testing shows an increase in performance with ~30% for a simple object when using generated code rather than metadata driven deserializer. In addition it seems to have positive impact on the memory aspect.

In case you are interested here is my experiment: https://github.com/mrange/T4JsonSerializer

@mrange
Copy link

mrange commented Aug 11, 2021

Could be worth investigating IMHO but I am sure there subtleties to the deserialization problem that makes it hard to create a one size fits all generated version. Perhaps there is value to generate a version that works in many use cases but also allow the metadata driven version when flexibility is required.

From my testing the overhead when using a static deserializer fell significantly but as the JSON parsing process itself takes sometime the overall performance gain is not as good.

@layomia
Copy link
Contributor Author

layomia commented Feb 7, 2023

Consider honoring ReadCommentHandling option - #81131.

@cirrusone
Copy link

Quite often we have project requirements of consuming external API's where data is provided as JSON strings, so we don't have the luxury of serializing our own data for better downstream performance. Some community driven projects seem to indicate that there are further possibilities of improving deserialization performance from JSON string data.

Below are some benchmarks using very simple data. Some of these community driven projects have since been abandoned or don't handle new types so SystemTextJson is always the safest option but it would be nice to have improved performance.

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1265/22H2/2022Update/SunValley2)
12th Gen Intel Core i9-12900H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=7.0.103
[Host] : .NET 7.0.3 (7.0.323.6910), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.3 (7.0.323.6910), X64 RyuJIT AVX2

Method Mean Error StdDev StdDev
SystemTextJson 255.84 ns 0.820 ns 0.767 ns 1.00
SystemTextJsonMetadataSrcGen 258.11 ns 0.389 ns 0.325 ns 1.01
SystemTextJsonDefaultSrcGen 254.68 ns 1.659 ns 1.552 ns 1.00
UTF8JSon 131.54 ns 0.800 ns 0.748 ns 0.51
UTF8JSon_Bytes 116.64 ns 0.277 ns 0.246 ns 0.46
SpanJSONUtf16 70.25 ns 0.394 ns 0.368 ns 0.27
SpanJSONUtf8 94.89 ns 0.641 ns 0.599 ns 0.37
SpanJSONUtf8_Bytes 76.01 ns 0.595 ns 0.557 ns 0.30
JsonSrcGen 65.51 ns 0.388 ns 0.344 ns 0.26

SourceGenTest.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>disable</ImplicitUsings>
    <Nullable>disable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
    <PackageReference Include="JsonSrcGen" Version="1.1.1" />
    <PackageReference Include="SpanJson" Version="4.0.0" />
    <PackageReference Include="Utf8Json" Version="1.3.7" />
  </ItemGroup>

</Project>

Program.cs:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using JsonSrcGen;

namespace SourceGenTest;

internal static class Program
{
    static void Main(string[] args)
    {

#if DEBUG
        new JsonDeserializeCompare().DebugTest();
#else
        var summary = BenchmarkRunner.Run<JsonDeserializeCompare>();
#endif

    }
}

public class JsonDeserializeCompare
{
    private string _jsonToDeserializeFromString;
    private byte[] _jsonToDeserializeFromBytesUtf8;
    private MetadataJsonClassContext _metadataJsonClassContext;
    private DefaultJsonClassContext _defaultJsonClassContext;

    private JsonSrcGenClass _jsonSrcGenClass;
    private JsonSrcGen.JsonConverter _jsonSrcGenConverter;
    public JsonDeserializeCompare()
    {
        _jsonToDeserializeFromString = "{\"Forename\":\"John\",\"Surname\":\"Smith\",\"Age\":42,\"Active\":true}";
        _jsonToDeserializeFromBytesUtf8 = Encoding.UTF8.GetBytes(_jsonToDeserializeFromString);

        _metadataJsonClassContext = new(new JsonSerializerOptions() { TypeInfoResolver = MetadataJsonClassContext.Default });
        _defaultJsonClassContext = new(new JsonSerializerOptions() { TypeInfoResolver = DefaultJsonClassContext.Default });

        _jsonSrcGenClass = new JsonSrcGenClass();
        _jsonSrcGenConverter = new JsonSrcGen.JsonConverter();

    }

    public void DebugTest()
    {
        JsonClass jsonClass1 = SystemTextJson();
        JsonClass jsonClass2 = SystemTextJsonMetadataSrcGen();
        JsonClass jsonClass3 = SystemTextJsonDefaultSrcGen();
        JsonClass jsonClass4 = UTF8JSon();
        JsonClass jsonClass5 = UTF8JSon_Bytes();
        JsonClass jsonClass6 = SpanJSONUtf16();
        JsonClass jsonClass7 = SpanJSONUtf8();
        JsonClass jsonClass8 = SpanJSONUtf8_Bytes();
        JsonSrcGenClass jsonClass9 = JsonSrcGen();

        //var reader = new Utf8JsonReader(_jsonToDeserializeUtf8, true, default);
        //reader.Read();

    }

    [Benchmark(Baseline = true)]
    public JsonClass SystemTextJson()
    {
        return System.Text.Json.JsonSerializer.Deserialize<JsonClass>(_jsonToDeserializeFromString);
    }

    [Benchmark]
    public JsonClass SystemTextJsonMetadataSrcGen()
    {
        return System.Text.Json.JsonSerializer.Deserialize(_jsonToDeserializeFromString, _metadataJsonClassContext.JsonClass);
    }

    [Benchmark]
    public JsonClass SystemTextJsonDefaultSrcGen()
    {
        return System.Text.Json.JsonSerializer.Deserialize(_jsonToDeserializeFromString, _defaultJsonClassContext.JsonClass);
    }

    [Benchmark]
    public JsonClass UTF8JSon()
    {
        return Utf8Json.JsonSerializer.Deserialize<JsonClass>(Encoding.UTF8.GetBytes(_jsonToDeserializeFromString));
    }

    [Benchmark]
    public JsonClass UTF8JSon_Bytes()
    {
        return Utf8Json.JsonSerializer.Deserialize<JsonClass>(_jsonToDeserializeFromBytesUtf8);
    }

    [Benchmark]
    public JsonClass SpanJSONUtf16()
    {
        return SpanJson.JsonSerializer.Generic.Utf16.Deserialize<JsonClass>(_jsonToDeserializeFromString);
    }

    [Benchmark]
    public JsonClass SpanJSONUtf8()
    {
        return SpanJson.JsonSerializer.Generic.Utf8.Deserialize<JsonClass>(Encoding.UTF8.GetBytes(_jsonToDeserializeFromString));
    }

    [Benchmark]
    public JsonClass SpanJSONUtf8_Bytes()
    {
        return SpanJson.JsonSerializer.Generic.Utf8.Deserialize<JsonClass>(_jsonToDeserializeFromBytesUtf8);
    }

    [Benchmark]
    public JsonSrcGenClass JsonSrcGen()
    {
        _jsonSrcGenConverter.FromJson(_jsonSrcGenClass, _jsonToDeserializeFromString);
        return _jsonSrcGenClass;
    }

}

public class JsonClass
{
    public string Forename { get; set; }

    public string Surname { get; set; }

    public int Age { get; set; }

    public bool Active { get; set; }
}

[JsonSrcGen.Json]
public class JsonSrcGenClass
{
    public string Forename { get; set; }

    public string Surname { get; set; }

    public int Age { get; set; }

    public bool Active { get; set; }
}

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(JsonClass))]
internal partial class MetadataJsonClassContext : JsonSerializerContext
{
}

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, GenerationMode = JsonSourceGenerationMode.Default)]
[JsonSerializable(typeof(JsonClass))]
internal partial class DefaultJsonClassContext : JsonSerializerContext
{
}

[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified, GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(JsonClass))]
internal partial class SerializationJsonClassContext : JsonSerializerContext
{
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Text.Json source-generator Indicates an issue with a source generator feature tenet-performance Performance related issue
Projects
None yet
Development

No branches or pull requests

5 participants