Fast-path PrefixEnumerator

This commit is contained in:
Ben Adams 2016-02-10 00:49:49 +00:00 committed by Ryan Nowak
parent a493a9756e
commit a33369acd8
1 changed files with 42 additions and 9 deletions

View File

@ -782,9 +782,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public struct PrefixEnumerator : IEnumerator<KeyValuePair<string, ModelStateEntry>> public struct PrefixEnumerator : IEnumerator<KeyValuePair<string, ModelStateEntry>>
{ {
private readonly ModelStateDictionary _dictionary; private readonly ModelStateDictionary _dictionary;
private readonly string _prefix; private string _prefix;
private bool _exactMatchUsed; private ExactMatchState _exactMatch;
private Dictionary<string, ModelStateEntry>.Enumerator _enumerator; private Dictionary<string, ModelStateEntry>.Enumerator _enumerator;
public PrefixEnumerator(ModelStateDictionary dictionary, string prefix) public PrefixEnumerator(ModelStateDictionary dictionary, string prefix)
@ -802,7 +802,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
_dictionary = dictionary; _dictionary = dictionary;
_prefix = prefix; _prefix = prefix;
_exactMatchUsed = false; _exactMatch = ExactMatchState.NotChecked;
_enumerator = default(Dictionary<string, ModelStateEntry>.Enumerator); _enumerator = default(Dictionary<string, ModelStateEntry>.Enumerator);
Current = default(KeyValuePair<string, ModelStateEntry>); Current = default(KeyValuePair<string, ModelStateEntry>);
} }
@ -831,26 +831,51 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
// ModelStateDictionary has a behavior where the first 'match' returned from iterating // ModelStateDictionary has a behavior where the first 'match' returned from iterating
// prefixes is the exact match for the prefix (if present). Only after looking for an // prefixes is the exact match for the prefix (if present). Only after looking for an
// exact match do we fall back to iteration to find 'starts-with' matches. // exact match do we fall back to iteration to find 'starts-with' matches.
if (!_exactMatchUsed) if (_exactMatch == ExactMatchState.NotChecked)
{ {
_exactMatchUsed = true;
_enumerator = _dictionary._innerDictionary.GetEnumerator(); _enumerator = _dictionary._innerDictionary.GetEnumerator();
ModelStateEntry entry; ModelStateEntry entry;
if (_dictionary.TryGetValue(_prefix, out entry)) if (_dictionary.TryGetValue(_prefix, out entry))
{ {
// Mark exact match as found
_exactMatch = ExactMatchState.Found;
Current = new KeyValuePair<string, ModelStateEntry>(_prefix, entry); Current = new KeyValuePair<string, ModelStateEntry>(_prefix, entry);
return true; return true;
} }
else
{
// Mark exact match tested for
_exactMatch = ExactMatchState.NotFound;
}
} }
while (_enumerator.MoveNext()) while (_enumerator.MoveNext())
{ {
if (string.Equals(_prefix, _enumerator.Current.Key, StringComparison.OrdinalIgnoreCase)) var key = _enumerator.Current.Key;
if (_exactMatch == ExactMatchState.NotFound)
{ {
// Skip this one. We've already handle the 'exact match' case. if (StartsWithPrefix(_prefix, key))
{
Current = _enumerator.Current;
return true;
}
continue;
} }
else if (StartsWithPrefix(_prefix, _enumerator.Current.Key)) else if (_exactMatch == ExactMatchState.ReferenceSet && Object.ReferenceEquals(_prefix, key))
{
// Fast path skip this one. Is the exact string reference set below.
}
else if (_exactMatch == ExactMatchState.Found &&
string.Equals(_prefix, key, StringComparison.OrdinalIgnoreCase))
{
// Update _prefix to be this exact string reference to enable fast path
_prefix = key;
// Mark exact reference set
_exactMatch = ExactMatchState.ReferenceSet;
// Skip this one. We've already handled the 'exact match' case.
}
else if (StartsWithPrefix(_prefix, key))
{ {
Current = _enumerator.Current; Current = _enumerator.Current;
return true; return true;
@ -862,10 +887,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
public void Reset() public void Reset()
{ {
_exactMatchUsed = false; _exactMatch = ExactMatchState.NotChecked;
_enumerator = default(Dictionary<string, ModelStateEntry>.Enumerator); _enumerator = default(Dictionary<string, ModelStateEntry>.Enumerator);
Current = default(KeyValuePair<string, ModelStateEntry>); Current = default(KeyValuePair<string, ModelStateEntry>);
} }
private enum ExactMatchState
{
NotChecked,
NotFound,
Found,
ReferenceSet
}
} }
} }
} }