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

Massive allocations of Func<Type, JsonTypeInfo> in System.Text.Json.JsonSerializerOptions+CachingContext.GetOrAddJsonTypeInfo(Type) #80430

Closed
cmeeren opened this issue Jan 10, 2023 · 5 comments · Fixed by #80437
Assignees
Labels
Milestone

Comments

@cmeeren
Copy link

cmeeren commented Jan 10, 2023

Create a plain F# console app targeting .NET 7, with this minimal repro code:

open System.Text.Json

let data = Array.init 1_000_000 (fun _ -> [box 1])
let json = JsonSerializer.Serialize(data)

This initializes an array with one million elements, each being an F# list with a boxed integer. It then serializes the data.

When I run a memory profiler (dotMemory), the top hitter in terms of allocation size is Func<Type, JsonTypeInfo> in System.Text.Json, specifically in GetOrAddJsonTypeInfo. It is allocated almost 900.000 times:

Allocated type : System.Func<Type, JsonTypeInfo>
  Objects : 896629
  Bytes   : 57384256

Allocated by
   100%  GetOrAddJsonTypeInfo • 54,73 MB / 54,73 MB • System.Text.Json.JsonSerializerOptions+CachingContext.GetOrAddJsonTypeInfo(Type)
     100%  GetTypeInfoInternal • 54,73 MB / - • System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type, Boolean, Boolean)
      ►  95,0%  ResolvePolymorphicConverter • 51,99 MB / - • System.Text.Json.Serialization.JsonConverter.ResolvePolymorphicConverter(Object, JsonTypeInfo, JsonSerializerOptions, WriteStack)
      ►  5,00%  InitializePolymorphicReEntry • 2,74 MB / - • System.Text.Json.WriteStackFrame.InitializePolymorphicReEntry(Type, JsonSerializerOptions)
      ► <0,01%  get_ElementTypeInfo • 128 B / - • System.Text.Json.Serialization.Metadata.JsonTypeInfo.get_ElementTypeInfo()
      ► <0,01%  GetTypeInfoForRootType • 64 B / - • System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type)

I assume the reason is that there is a lambda or similar somewhere that is not being optimized/cached by the C# compiler.

You may want to adjust your code to prevent these allocations.

Maybe related: dotnet/roslyn#5835 and linked issues

@cmeeren cmeeren added the tenet-performance Performance related issue label Jan 10, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 10, 2023
@ghost
Copy link

ghost commented Jan 10, 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

Create a plain F# console app targeting .NET 7, with this minimal repro code:

open System.Text.Json

let data = Array.init 1_000_000 (fun _ -> [box 1])
let json = JsonSerializer.Serialize(data)

This initializes an array with one million elements, each being an F# list with a boxed integer. It then serializes the data.

When I run a memory profiler (dotMemory), the top hitter in terms of allocation size is Func<Type, JsonTypeInfo> in System.Text.Json, specifically in GetOrAddJsonTypeInfo. It is allocated almost 900.000 times:

Allocated type : System.Func<Type, JsonTypeInfo>
  Objects : 896629
  Bytes   : 57384256

Allocated by
   100%  GetOrAddJsonTypeInfo • 54,73 MB / 54,73 MB • System.Text.Json.JsonSerializerOptions+CachingContext.GetOrAddJsonTypeInfo(Type)
     100%  GetTypeInfoInternal • 54,73 MB / - • System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type, Boolean, Boolean)
      ►  95,0%  ResolvePolymorphicConverter • 51,99 MB / - • System.Text.Json.Serialization.JsonConverter.ResolvePolymorphicConverter(Object, JsonTypeInfo, JsonSerializerOptions, WriteStack)
      ►  5,00%  InitializePolymorphicReEntry • 2,74 MB / - • System.Text.Json.WriteStackFrame.InitializePolymorphicReEntry(Type, JsonSerializerOptions)
      ► <0,01%  get_ElementTypeInfo • 128 B / - • System.Text.Json.Serialization.Metadata.JsonTypeInfo.get_ElementTypeInfo()
      ► <0,01%  GetTypeInfoForRootType • 64 B / - • System.Text.Json.JsonSerializerOptions.GetTypeInfoForRootType(Type)

I assume the reason is that there is a lambda or similar somewhere that is not being optimized/cached by the C# compiler.

You may want to adjust your code to prevent these allocations.

Maybe related: dotnet/roslyn#5835 and linked issues

Author: cmeeren
Assignees: -
Labels:

area-System.Text.Json, tenet-performance

Milestone: -

@stephentoub stephentoub self-assigned this Jan 10, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jan 10, 2023
@eiriktsarpalis
Copy link
Member

🙀 I was today years old when I realized these delegates aren't being cached.

@eiriktsarpalis eiriktsarpalis removed the untriaged New issue has not been triaged by the area owner label Jan 10, 2023
@eiriktsarpalis eiriktsarpalis added this to the 8.0.0 milestone Jan 10, 2023
@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Jan 10, 2023

Even though this will mostly impact collections with polymorphic elements, we should probably service this in .NET 7.

@cmeeren
Copy link
Author

cmeeren commented Jan 10, 2023

🙀 I was today years old when I realized these delegates aren't being cached.

We're of the same age, then 😅 (Based on my very recent experience, seems F# doesn't cache these, either. @dsyme, is that something to consider, or is there no point in raising an F# issue about that?)

@vzarytovskii
Copy link
Member

🙀 I was today years old when I realized these delegates aren't being cached.

We're of the same age, then 😅 (Based on my very recent experience, seems F# doesn't cache these, either. @dsyme Don Syme FTE, is that something to consider, or is there no point in raising an F# issue about that?)

Please create (or search for) a suggestion at https://github.com/fsharp/fslang-suggestions/issues, we can follow-up there.

@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 11, 2023
@ghost ghost added in-pr There is an active PR which will close this issue when it is merged and removed in-pr There is an active PR which will close this issue when it is merged labels Jan 11, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Feb 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants