Optimize TemplateMatcher
Replaces a bunch of dictionary operations with indexing into an array by doing some caching. Also eliminating an enumerator allocation by changing from IReadOnlyDictionary to RouteValueDictionary.
This commit is contained in:
parent
8f850f2a3e
commit
226cfb1e0d
|
|
@ -14,11 +14,15 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
private const string SeparatorString = "/";
|
private const string SeparatorString = "/";
|
||||||
private const char SeparatorChar = '/';
|
private const char SeparatorChar = '/';
|
||||||
|
|
||||||
|
// Perf: This is a cache to avoid looking things up in 'Defaults' each request.
|
||||||
|
private readonly bool[] _hasDefaultValue;
|
||||||
|
private readonly object[] _defaultValues;
|
||||||
|
|
||||||
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
||||||
|
|
||||||
public TemplateMatcher(
|
public TemplateMatcher(
|
||||||
RouteTemplate template,
|
RouteTemplate template,
|
||||||
IReadOnlyDictionary<string, object> defaults)
|
RouteValueDictionary defaults)
|
||||||
{
|
{
|
||||||
if (template == null)
|
if (template == null)
|
||||||
{
|
{
|
||||||
|
|
@ -27,11 +31,37 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
|
|
||||||
Template = template;
|
Template = template;
|
||||||
Defaults = defaults ?? new RouteValueDictionary();
|
Defaults = defaults ?? new RouteValueDictionary();
|
||||||
|
|
||||||
|
// Perf: cache the default value for each parameter (other than complex segments).
|
||||||
|
_hasDefaultValue = new bool[Template.Segments.Count];
|
||||||
|
_defaultValues = new object[Template.Segments.Count];
|
||||||
|
|
||||||
|
for (var i = 0; i < Template.Segments.Count; i++)
|
||||||
|
{
|
||||||
|
var segment = Template.Segments[i];
|
||||||
|
if (!segment.IsSimple)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var part = segment.Parts[0];
|
||||||
|
if (!part.IsParameter)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
object value;
|
||||||
|
if (Defaults.TryGetValue(part.Name, out value))
|
||||||
|
{
|
||||||
|
_hasDefaultValue[i] = true;
|
||||||
|
_defaultValues[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, object> Defaults { get; private set; }
|
public RouteValueDictionary Defaults { get; }
|
||||||
|
|
||||||
public RouteTemplate Template { get; private set; }
|
public RouteTemplate Template { get; }
|
||||||
|
|
||||||
public RouteValueDictionary Match(PathString path)
|
public RouteValueDictionary Match(PathString path)
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +107,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
// For a parameter, validate that it's a has some length, or we have a default, or it's optional.
|
// For a parameter, validate that it's a has some length, or we have a default, or it's optional.
|
||||||
var part = routeSegment.Parts[0];
|
var part = routeSegment.Parts[0];
|
||||||
if (requestSegment.Length == 0 &&
|
if (requestSegment.Length == 0 &&
|
||||||
!Defaults.ContainsKey(part.Name) &&
|
!_hasDefaultValue[i] &&
|
||||||
!part.IsOptional)
|
!part.IsOptional)
|
||||||
{
|
{
|
||||||
// There's no value for this parameter, the route can't match.
|
// There's no value for this parameter, the route can't match.
|
||||||
|
|
@ -125,7 +155,7 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
// If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
|
// If we get here, this is a simple segment with a parameter. We need it to be optional, or for the
|
||||||
// defaults to have a value.
|
// defaults to have a value.
|
||||||
Debug.Assert(routeSegment.IsSimple && part.IsParameter);
|
Debug.Assert(routeSegment.IsSimple && part.IsParameter);
|
||||||
if (!Defaults.ContainsKey(part.Name) && !part.IsOptional)
|
if (!_hasDefaultValue[i] && !part.IsOptional)
|
||||||
{
|
{
|
||||||
// There's no default for this (non-optional) parameter so it can't match.
|
// There's no default for this (non-optional) parameter so it can't match.
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -151,11 +181,8 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// It's ok for a catch-all to produce a null value
|
// It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
|
||||||
object defaultValue;
|
values.Add(part.Name, _defaultValues[i]);
|
||||||
Defaults.TryGetValue(part.Name, out defaultValue);
|
|
||||||
|
|
||||||
values.Add(part.Name, defaultValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A catch-all has to be the last part, so we're done.
|
// A catch-all has to be the last part, so we're done.
|
||||||
|
|
@ -172,10 +199,9 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
object defaultValue;
|
if (_hasDefaultValue[i])
|
||||||
if (Defaults.TryGetValue(part.Name, out defaultValue))
|
|
||||||
{
|
{
|
||||||
values.Add(part.Name, defaultValue);
|
values.Add(part.Name, _defaultValues[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,10 +226,9 @@ namespace Microsoft.AspNet.Routing.Template
|
||||||
Debug.Assert(part.IsParameter);
|
Debug.Assert(part.IsParameter);
|
||||||
|
|
||||||
// It's ok for a catch-all to produce a null value
|
// It's ok for a catch-all to produce a null value
|
||||||
object defaultValue;
|
if (_hasDefaultValue[i] || part.IsCatchAll)
|
||||||
if (Defaults.TryGetValue(part.Name, out defaultValue) || part.IsCatchAll)
|
|
||||||
{
|
{
|
||||||
values.Add(part.Name, defaultValue);
|
values.Add(part.Name, _defaultValues[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -915,13 +915,13 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
||||||
private static void RunTest(
|
private static void RunTest(
|
||||||
string template,
|
string template,
|
||||||
string path,
|
string path,
|
||||||
IReadOnlyDictionary<string, object> defaults,
|
RouteValueDictionary defaults,
|
||||||
IDictionary<string, object> expected)
|
IDictionary<string, object> expected)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var matcher = new TemplateMatcher(
|
var matcher = new TemplateMatcher(
|
||||||
TemplateParser.Parse(template),
|
TemplateParser.Parse(template),
|
||||||
defaults ?? new Dictionary<string, object>());
|
defaults ?? new RouteValueDictionary());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var match = matcher.Match(new PathString(path));
|
var match = matcher.Match(new PathString(path));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue