Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Minor deserialization perf improvements for collections #40889

Merged
merged 2 commits into from
Sep 9, 2019

Conversation

steveharter
Copy link
Member

  • Defer calls to ReadStack.JsonPath until an exception needs that information (avoids String and StringBuilder allocs).
  • Remove dead code and unused variables

Benchmarks for deserializing single collection System.Text.Json.Serialization.Tests.ReadJson<ImmutableDictionary<string, string>>

Before
|                   Method |     Mean |     Error |    StdDev |   Median |      Min |      Max | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|------------------------- |---------:|----------:|----------:|---------:|---------:|---------:|------------:|------------:|------------:|--------------------:|
|    DeserializeFromString | 65.60 us | 0.6393 us | 0.5668 us | 65.50 us | 64.64 us | 66.60 us |      6.6489 |      0.5319 |           - |            40.87 KB |
| DeserializeFromUtf8Bytes | 63.73 us | 0.9506 us | 0.8891 us | 63.58 us | 62.40 us | 65.20 us |      5.5444 |      0.5040 |           - |            35.07 KB |
|    DeserializeFromStream | 64.76 us | 1.2135 us | 1.0758 us | 64.87 us | 63.33 us | 66.52 us |      5.6921 |      0.5175 |           - |            35.14 KB

After
|                   Method |     Mean |     Error |    StdDev |   Median |      Min |      Max | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|------------------------- |---------:|----------:|----------:|---------:|---------:|---------:|------------:|------------:|------------:|--------------------:|
|    DeserializeFromString | 64.71 us | 0.8625 us | 0.8067 us | 64.36 us | 63.72 us | 66.20 us |      6.4317 |      0.7718 |           - |            40.75 KB |
| DeserializeFromUtf8Bytes | 62.82 us | 0.6094 us | 0.5701 us | 62.72 us | 62.14 us | 63.98 us |      5.4781 |      0.7470 |           - |            34.94 KB |
|    DeserializeFromStream | 63.31 us | 0.6213 us | 0.5812 us | 63.20 us | 62.42 us | 64.38 us |      5.5710 |      0.7597 |           - |            35.01 KB |

@steveharter steveharter added this to the 5.0 milestone Sep 6, 2019
@steveharter steveharter self-assigned this Sep 6, 2019
yield return (TRuntimeProperty)item;
}
}

private IEnumerable<TDeclaredProperty> CreateGenericTDeclaredPropertyIEnumerable(IList sourceList)
{
foreach (object item in sourceList)
{
yield return (TDeclaredProperty)item;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated to your PR, but you might consider using Enumerable.Cast instead of this method:

public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source)
{
if (source is IEnumerable<TResult> typedSource)
{
return typedSource;
}
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
return CastIterator<TResult>(source);
}
private static IEnumerable<TResult> CastIterator<TResult>(IEnumerable source)
{
foreach (object? obj in source)
{
yield return (TResult)obj!;
}
}
}

Copy link
Member Author

Choose a reason for hiding this comment

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

@layomia can you go through this code and update the casts per suggestion above?

{
IDictionary collection = null;

if (!options.TryGetCreateRangeDelegate(delegateKey, out ImmutableCollectionCreator creator) ||
!creator.CreateImmutableDictionary(sourceDictionary, out collection))
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, jsonPath);
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(collectionType, state.JsonPath);
Copy link
Member

Choose a reason for hiding this comment

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

nit: I was playing around with the deserializer code, and I think JsonPath should be changed to a method rather than being a property (GetJsonPath()). Making it a property gives the wrong impression that its cheap, when it is actually doing the work to build the path.

It might be worth taking pieces from this commit and incorporate it into yours:
ahsonkhan@c76bbd6

Copy link
Member Author

@steveharter steveharter Sep 9, 2019

Choose a reason for hiding this comment

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

Thanks. Yes the main reason for this PR is because JsonPath is not cheap. I'm make the changes.

Also making a method vs property affects debuggability, so I'll update the [DebuggerDisplay] to call the method.

@@ -304,41 +304,25 @@ public override IEnumerable CreateImmutableCollectionInstance(Type collectionTyp
// Creates an IEnumerable<TRuntimePropertyType> and populates it with the items in the
// sourceList argument then uses the delegateKey argument to identify the appropriate cached
// CreateRange<TRuntimePropertyType> method to create and return the desired immutable collection type.
public override IDictionary CreateImmutableDictionaryInstance(Type collectionType, string delegateKey, IDictionary sourceDictionary, string jsonPath, JsonSerializerOptions options)
public override IDictionary CreateImmutableDictionaryInstance(ref ReadStack state, Type collectionType, string delegateKey, IDictionary sourceDictionary, JsonSerializerOptions options)
Copy link
Member

Choose a reason for hiding this comment

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

Why re-order the parameters? Why not replace the string jsonPath parameter with ref ReadStack state in the same order? This applies for all the changes here.

Copy link
Member Author

Choose a reason for hiding this comment

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

The ReadStack and jsonPath parameters are not equivalent.

Having ReadStack first is being consistent with the calling methods (e.g. CreateFromDictionary(ref ReadStack state, IDictionary sourceDictionary, JsonSerializerOptions options)

@layomia
Copy link
Contributor

layomia commented Sep 9, 2019

I'll create a PR incorporating #40889 (comment) & applicable pieces from #40889 (comment) when this PR is merged.

@steveharter steveharter merged commit 27ddbe9 into dotnet:master Sep 9, 2019
@steveharter steveharter deleted the MiscPerf branch September 9, 2019 21:55
steveharter added a commit to steveharter/dotnet_corefx that referenced this pull request Oct 14, 2019
steveharter added a commit to steveharter/dotnet_corefx that referenced this pull request Oct 14, 2019
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants