Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Use a prefix tree as a backing store for ModelStateDictionary #4108

Closed
wants to merge 2 commits into from

Conversation

pranavkm
Copy link
Contributor

No description provided.

@pranavkm
Copy link
Contributor Author

cc @rynowak

@@ -20,7 +22,10 @@ public class ModelStateDictionary : IDictionary<string, ModelStateEntry>
/// </summary>
public static readonly int DefaultMaxAllowedErrors = 200;

private readonly Dictionary<string, ModelStateEntry> _innerDictionary;
private static readonly StringSegment EmptyStringSegment = new StringSegment(buffer: string.Empty);
Copy link
Member

Choose a reason for hiding this comment

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

StringSegment is a struct, there's really no benefit to caching this.

@rynowak
Copy link
Member

rynowak commented Feb 17, 2016

var currentRoot = _root;
var previousIndex = 0;
int index;
while ((index = key.IndexOfAny(Delimiters, previousIndex)) != -1)
Copy link
Member

Choose a reason for hiding this comment

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

It would be great to show sample input in comments

@pranavkm
Copy link
Contributor Author

Updated. Working on adding more tests

}

/// <inheritdoc />
public bool Contains(KeyValuePair<string, ModelStateEntry> item)
bool ICollection<KeyValuePair<string, ModelStateEntry>>.Contains(KeyValuePair<string, ModelStateEntry> item)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure how to go about this - the equality comparison for item.Value will always return false since we create a copy of the one provided to us in Add or indexer. Should we just have this return false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same goes for Remove(KeyValuePair<string, ModelStateEntry>).

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I guess it's a bit of an issue that there's no deep equality for MSE.

IMO the better thing to do is throw an exception. (NotSupportedException)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}

foreach (var item in 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.

Could do this recursively to avoid the enumerator allocation. Not sure how much we care about CopyTo.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not worried about it.

@rynowak
Copy link
Member

rynowak commented Feb 19, 2016

⌚ for a few smaller comments

searchKey.Offset,
searchKey.Length,
StringComparison.OrdinalIgnoreCase);
}
Copy link
Member

Choose a reason for hiding this comment

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

In previous iteration, property "AB" would come before property "Z". Not sure how much difference the intrinsic order makes but why place shorter property names first?

Copy link
Member

Choose a reason for hiding this comment

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

Put another way, why not check the length only if string.Compare() returns 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's going to be faster to compare lengths first than run through the string.

Copy link
Member

Choose a reason for hiding this comment

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

It's going to be faster to compare lengths first than run through the string.

Might only be visible in BadRequest(ModelState) responses and where actions use the enumerators. But the (early) optimization isn't worth this bizarre order.

Copy link
Member

Choose a reason for hiding this comment

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

We agreed offline to fix this up i.e. make the order sane. Correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm leaving this as is. The dictionary has no guarantees of ordering and I think it's fine the way we have things now. If we need ordering for BadRequest we should change it there.

@dougbu
Copy link
Member

dougbu commented Mar 11, 2016

⌚ for a couple more doc comments. And perhaps Visit() could be a bit shorter.

@dougbu
Copy link
Member

dougbu commented Mar 11, 2016

Larger question: With this change being allocation-neutral, is it worth going ahead? In particular is there a RPS improvement or does an execution hotspot go away?

If not, I'm a fan of the improved reliability of the ValidationSummary() error ordering. And the reduced public ModelStateDictionary surface might be worthwhile on its own (though a breaking change). But are these positive aspects enough to justify the extra complexity and code?

@rynowak
Copy link
Member

rynowak commented Mar 14, 2016

Larger question: With this change being allocation-neutral, is it worth going ahead? In particular is there a RPS improvement or does an execution hotspot go away?

Yes. This change is focused on reducing the cost of things like GetValidationState which was implemented as a linear scan.

@dougbu
Copy link
Member

dougbu commented Mar 14, 2016

the cost of things like GetValidationState which was implemented as a linear scan

If dotTrace should show a solid improvement, suggest measuring.

@pranavkm
Copy link
Contributor Author

Sampling profiles of RouteMiddleware.Invoke before and after the change:

Before

image

ModelStateDictionary.GetFieldValidationState accounts for 141ms of a total of 1609ms (~10%) time taken for RouteMiddleware.Invoke.

After

image

ModelStateDictionary.GetFieldValidationState accounts for 31ms of a total of 3141ms (~1%) time taken for RouteMiddleware.Invoke.

@pranavkm
Copy link
Contributor Author

Updated


using System.Collections.Generic;

namespace Microsoft.AspNetCore.Mvc.ModelBinding
Copy link
Member

Choose a reason for hiding this comment

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

👏 for fixing the filename

@dougbu
Copy link
Member

dougbu commented Mar 16, 2016

⌚ for #4108 (diff) resolution.

@pranavkm
Copy link
Contributor Author

Updated.

@dougbu
Copy link
Member

dougbu commented Mar 17, 2016

:shipit: and please file the prefixed key follow-up issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants