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

Remove the second type parameter from CastingConverter #80755

Conversation

eiriktsarpalis
Copy link
Member

@eiriktsarpalis eiriktsarpalis commented Jan 17, 2023

The CastingConverter<TSource, TTarget> class is an internal adapter used for custom converters whose declared type doesn't match that of the serialized member they are being assigned to. The problem with the current implementation is that CastingConverter uses two type parameters, which is contributing to a quadratic explosion of generic specializations in NativeAOT:

System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Text.Json.JsonElement>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Boolean>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Byte>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Char>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateOnly>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateTime>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateTimeOffset>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Decimal>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Double>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Guid>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int16>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int32>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int64>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.SByte>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Single>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.TimeOnly>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.TimeSpan>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt16>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt32>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt64>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.__Canon>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Text.Json.JsonElement,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Text.Json.JsonElement,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Boolean,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Boolean,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Byte,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Byte,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Char,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Char,System.__Canon>.      

This PR relaxes the implementation so that CastingConverter references and consumes the underlying converter as a weakly typed JsonConverter instance. This change can regress runtime performance in certain scenaria due to added boxing/virtual calls, but this should only impact members that are using converters polymorphically -- a relatively rare occurrence.

cc @eerhardt @jkotas

@ghost
Copy link

ghost commented Jan 17, 2023

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

Issue Details

The CastingConverter<TSource, TTarget> class is an internal adapter used for custom converters whose declared type doesn't match that of the serialized member they are being assigned to. The problem with the current implementation is that CastingConverter uses two type parameters, which is contributing to a quadratic explosion of generic specializations in NativeAOT:

System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Text.Json.JsonElement>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Boolean>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Byte>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Char>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateOnly>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateTime>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateTimeOffset>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Decimal>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Double>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Guid>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int16>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int32>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int64>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.SByte>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Single>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.TimeOnly>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.TimeSpan>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt16>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt32>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt64>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.__Canon>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Text.Json.JsonElement,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Text.Json.JsonElement,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Boolean,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Boolean,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Byte,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Byte,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Char,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Char,System.__Canon>.      

This PR relaxes the implementation so that CastingConverter references and consumes the underlying converter as a weakly typed JsonConverter instance. This change can regress runtime performance in certain scenaria due to added boxing/virtual calls, but this should only impact members that are using converters polymorphically -- a relatively rare occurrence.

cc @eerhardt

Author: eiriktsarpalis
Assignees: eiriktsarpalis
Labels:

area-System.Text.Json

Milestone: 8.0.0

@@ -138,15 +143,20 @@ internal static bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state)
// This is used internally to quickly determine the type being converted for JsonConverter<T>.
internal abstract Type TypeToConvert { get; }

internal abstract bool OnTryReadAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state, out object? value);
internal abstract bool TryReadAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state, out object? value);
internal abstract object? ReadAsObject(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);
Copy link
Member Author

@eiriktsarpalis eiriktsarpalis Jan 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though this change is adding new methods to JsonConverter this should increase code size linearly over the number of generic specializations. This should still contribute to a net decrease of code size given that CastingConverter contributes to quadratic growth over the number of types being used. I will follow up with real numbers validating this hypothesis.

@eerhardt
Copy link
Member

which is contributing to a quadratic explosion of generic specializations in NativeAOT:

Is it possible to get before/after size numbers for this change? If you need a test app, the one we are using in our perf measurements is here.

@eiriktsarpalis
Copy link
Member Author

I compared the publish AOT size for the Goldilocks app between main and the PR branch. I'm seeing a ~7MB improvement in the size of the publish folder, from 120 MB down to 113 MB.

@eiriktsarpalis
Copy link
Member Author

eiriktsarpalis commented Jan 19, 2023

STJ Numbers from the mstat dump of the Goldilocks app. Seeing just over 1MB of size reduction overall:

21156   publish-main/Goldilocks
20064   publish-pr/Goldilocks

main

// ********** Types Total Size 3,327,268
System.Text.Json                         476,148

// ********** Size By Namespace
System.Text.Json.Serialization.Converters 876,578
System.Text.Json.Serialization.Metadata  607,084
System.Text.Json.Serialization           361,833
System.Text.Json                         203,356

PR

// ********** Types Total Size 3,172,132
System.Text.Json                         320,884

// ********** Size By Namespace
System.Text.Json.Serialization.Metadata  605,298
System.Text.Json.Serialization           397,892
System.Text.Json                         208,571
System.Text.Json.Serialization.Converters 200,477

@eerhardt
Copy link
Member

/benchmark json aspnet-citrine-win libs

@pr-benchmarks
Copy link

pr-benchmarks bot commented Jan 19, 2023

Benchmark started for json on aspnet-citrine-win with libs. Logs: link

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look over the code (I'm no expert in this area), but I think this is a great change. If there are no runtime perf degradations, I think this is a great NativeAOT size win (5% of the new dotnet new api -aot app).

@pr-benchmarks
Copy link

pr-benchmarks bot commented Jan 19, 2023

json - aspnet-citrine-win

application json.base json.pr
CPU Usage (%) 80 87 +8.75%
Cores usage (%) 2,229 2,433 +9.15%
Working Set (MB) 73 74 +1.37%
Private Memory (MB) 100 101 +1.00%
Build Time (ms) 4,979 3,287 -33.98%
Start Time (ms) 417 418 +0.24%
Published Size (KB) 96,702 96,702 0.00%
Symbols Size (KB) 52 52 0.00%
.NET Core SDK Version 8.0.100-alpha.1.23069.6 8.0.100-alpha.1.23069.6
load json.base json.pr
CPU Usage (%) 72 74 +2.78%
Cores usage (%) 2,026 2,084 +2.86%
Working Set (MB) 119 119 0.00%
Private Memory (MB) 363 363 0.00%
Start Time (ms) 0 0
First Request (ms) 85 85 0.00%
Requests/sec 1,059,909 1,095,251 +3.33%
Requests 16,004,210 16,537,175 +3.33%
Mean latency (ms) 0.89 1.11 +24.72%
Max latency (ms) 36.84 59.44 +61.35%
Bad responses 0 0
Socket errors 0 0
Read throughput (MB/s) 147.58 152.50 +3.33%
Latency 50th (ms) 0.32 0.31 -4.64%
Latency 75th (ms) 0.77 0.83 +8.37%
Latency 90th (ms) 2.34 3.10 +32.48%
Latency 99th (ms) 7.02 10.31 +46.87%

Copy link
Member

@krwq krwq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome win, thanks!

@krwq
Copy link
Member

krwq commented Jan 20, 2023

:shipit:

@krwq krwq added the size-reduction Issues impacting final app size primary for size sensitive workloads label Jan 20, 2023
@ghost
Copy link

ghost commented Jan 20, 2023

Tagging subscribers to 'size-reduction': @eerhardt, @SamMonoRT, @marek-safar
See info in area-owners.md if you want to be subscribed.

Issue Details

The CastingConverter<TSource, TTarget> class is an internal adapter used for custom converters whose declared type doesn't match that of the serialized member they are being assigned to. The problem with the current implementation is that CastingConverter uses two type parameters, which is contributing to a quadratic explosion of generic specializations in NativeAOT:

System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Text.Json.JsonElement>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Boolean>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Byte>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Char>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateOnly>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateTime>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.DateTimeOffset>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Decimal>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Double>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Guid>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int16>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int32>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Int64>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.SByte>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Single>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.TimeOnly>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.TimeSpan>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt16>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt32>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.UInt64>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.__Canon,System.__Canon>.                                                                           384
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Text.Json.JsonElement,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Text.Json.JsonElement,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Boolean,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Boolean,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Byte,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Byte,System.__Canon>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Char,System.Collections.Generic.KeyValuePair`2<System.__Canon,System.__Canon>>.                                                                           376
System.Text.Json.Serialization.Converters.CastingConverter`2<System.Char,System.__Canon>.      

This PR relaxes the implementation so that CastingConverter references and consumes the underlying converter as a weakly typed JsonConverter instance. This change can regress runtime performance in certain scenaria due to added boxing/virtual calls, but this should only impact members that are using converters polymorphically -- a relatively rare occurrence.

cc @eerhardt @jkotas

Author: eiriktsarpalis
Assignees: eiriktsarpalis
Labels:

area-System.Text.Json, size-reduction

Milestone: 8.0.0

@eiriktsarpalis eiriktsarpalis merged commit a04aaeb into dotnet:main Jan 20, 2023
@eiriktsarpalis eiriktsarpalis deleted the castingconverter-remove-generic-parameter branch January 20, 2023 16:15
mdh1418 pushed a commit to mdh1418/runtime that referenced this pull request Jan 24, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Feb 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Text.Json size-reduction Issues impacting final app size primary for size sensitive workloads
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants