Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Address misc feedback and issues from recent perf changes#41414

Merged
steveharter merged 4 commits intodotnet:masterfrom
steveharter:PerfRecap
Oct 8, 2019
Merged

Address misc feedback and issues from recent perf changes#41414
steveharter merged 4 commits intodotnet:masterfrom
steveharter:PerfRecap

Conversation

@steveharter
Copy link
Contributor

@steveharter steveharter commented Sep 27, 2019

Address late feedback from previous perf PR #41098 and other misc perf-related deserialization changes.

Results in deserialization perf increase of ~1% - ~2% for simple object.

@steveharter steveharter added this to the 5.0 milestone Sep 27, 2019
@steveharter steveharter self-assigned this Sep 27, 2019
{
int len = JsonClassInfo.PropertyCacheArray.Length;
if (PropertyEnumeratorIndex < len)
if (PropertyEnumeratorIndex > len)

Choose a reason for hiding this comment

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

I am not fully understanding this logic. Can you explain what we are trying to do here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The extension property is always the last property in PropertyCacheArray so this is checking to see if the extension property is writing or whether it is finished.

The extension property which is different than other properties because although it is a dictionary property we want to serialize each element in the dictionary as a property of the POCO instead of a dictionary element thus we need some extra state to track whether it is being serialized.

if (maxBytes > options.DefaultBufferSize)
{
// Get the actual byte count in order to handle large input.
maxBytes = JsonReaderHelper.GetUtf8ByteCount(json.AsSpan());

Choose a reason for hiding this comment

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

nit: Given this throws for certain large string lengths (when the return count won't fit int.MaxValue), should this be documented as an exception on the method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If so we should also document every other Deserialize method as well. Perhaps create a doc issue?

Copy link

@ahsonkhan ahsonkhan Oct 3, 2019

Choose a reason for hiding this comment

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

Do the other deserialize methods have this transcoding-based exception though? If so, then yes, we should. Will create an issue on https://github.com/dotnet/dotnet-api-docs.

// higher than the threshold which is options.DefaultBufferSize.
Span<byte> utf8 = json.Length <= (ArrayPoolMaxSizeBeforeUsingNormalAlloc / JsonConstants.MaxExpansionFactorWhileTranscoding) ?
tempArray = ArrayPool<byte>.Shared.Rent(json.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) :
new byte[JsonReaderHelper.GetUtf8ByteCount(json.AsSpan())];
Copy link
Member

Choose a reason for hiding this comment

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

Why not just always use Rent? If it's too large for the pool, it'll just allocate it itself.

Choose a reason for hiding this comment

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

From the previous commit (which removed the comment):
https://github.com/dotnet/corefx/pull/41414/files/a8cd02e62d6bd2d4686ff86124fdc31e233ca42d..218e1c0d306dde4441927fdcecacd071295a145c

// and because we can avoid calling Clear().

And from above:

// For performance, avoid obtaining the actual byte count unless the memory usage may be
// higher than the threshold

Copy link
Member

Choose a reason for hiding this comment

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

avoid obtaining the actual byte count unless the memory usage may be higher than the threshold

Computing an accurate byte count doesn't prevent you from still using ArrayPool.

and because we can avoid calling Clear()

The benefit analysis of clearing all of these arrays is still not obvious to me.

Choose a reason for hiding this comment

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

Computing an accurate byte count doesn't prevent you from still using ArrayPool.

I believe the intention here is to avoid doing the extra work to get an accurate byte count when we are using the arraypool anyway and save that cost (asking for 4k from the pool vs 12k isn't that much different, so asking for the worst case is good enough). However, when allocating a regular array, over-asking by up to 3x is probably too expensive, so asking for the exact amount needed by incurring the cost of getting the exact count is worth the trade-off.

With your suggestion, we'd always have to always get the accurate byte count, regardless of whether the pool was allocating itself (because it got exhausted/we need over 1 MB), or not. Maybe that approach is good enough but there are certainly some trade-offs here. @steveharter, what are your thoughts on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes there are two things going on here:

  • Avoid asking for a transcoded byte count when a threshold is surpassed
  • The threshold selected happens to be the same as ArrayPool's current implementation where it just does a normal alloc, so a call to Clear() can be avoided

Given that, yes the call to Clear() is insignificant so we could just use ArrayPool always.

@ahsonkhan
Copy link

It is hard to review just the latest changes. Can you avoid force pushing and keep the individual commits going forward (otherwise, I end up re-reviewing the exact same lines of code)?

Copy link

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

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

Other than the threshold typo in tests and some test nits/questions, looks good.

@steveharter
Copy link
Contributor Author

Test failures not related:

System.Data.OleDb.Tests
System.Diagnostics.EventLog.Tests

@steveharter steveharter merged commit 8655ef9 into dotnet:master Oct 8, 2019
@steveharter steveharter deleted the PerfRecap branch October 8, 2019 20:02
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.

3 participants