Skip to content

Commit

Permalink
Optimize TemplateMatcher
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rynowak committed Jan 5, 2016
1 parent 8f850f2 commit 226cfb1
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 18 deletions.
57 changes: 41 additions & 16 deletions src/Microsoft.AspNet.Routing/Template/TemplateMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ public class TemplateMatcher
private const string SeparatorString = "/";
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 };

public TemplateMatcher(
RouteTemplate template,
IReadOnlyDictionary<string, object> defaults)
RouteValueDictionary defaults)
{
if (template == null)
{
Expand All @@ -27,11 +31,37 @@ public TemplateMatcher(

Template = template;
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)
{
Expand Down Expand Up @@ -77,7 +107,7 @@ public RouteValueDictionary Match(PathString path)
// 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];
if (requestSegment.Length == 0 &&
!Defaults.ContainsKey(part.Name) &&
!_hasDefaultValue[i] &&
!part.IsOptional)
{
// There's no value for this parameter, the route can't match.
Expand Down Expand Up @@ -125,7 +155,7 @@ public RouteValueDictionary Match(PathString path)
// 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.
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.
return null;
Expand All @@ -151,11 +181,8 @@ public RouteValueDictionary Match(PathString path)
}
else
{
// It's ok for a catch-all to produce a null value
object defaultValue;
Defaults.TryGetValue(part.Name, out defaultValue);

values.Add(part.Name, defaultValue);
// It's ok for a catch-all to produce a null value, so we don't check _hasDefaultValue.
values.Add(part.Name, _defaultValues[i]);
}

// A catch-all has to be the last part, so we're done.
Expand All @@ -172,10 +199,9 @@ public RouteValueDictionary Match(PathString path)
}
else
{
object defaultValue;
if (Defaults.TryGetValue(part.Name, out defaultValue))
if (_hasDefaultValue[i])
{
values.Add(part.Name, defaultValue);
values.Add(part.Name, _defaultValues[i]);
}
}
}
Expand All @@ -200,10 +226,9 @@ public RouteValueDictionary Match(PathString path)
Debug.Assert(part.IsParameter);

// It's ok for a catch-all to produce a null value
object defaultValue;
if (Defaults.TryGetValue(part.Name, out defaultValue) || part.IsCatchAll)
if (_hasDefaultValue[i] || part.IsCatchAll)
{
values.Add(part.Name, defaultValue);
values.Add(part.Name, _defaultValues[i]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -915,13 +915,13 @@ private TemplateMatcher CreateMatcher(string template, object defaults = null)
private static void RunTest(
string template,
string path,
IReadOnlyDictionary<string, object> defaults,
RouteValueDictionary defaults,
IDictionary<string, object> expected)
{
// Arrange
var matcher = new TemplateMatcher(
TemplateParser.Parse(template),
defaults ?? new Dictionary<string, object>());
defaults ?? new RouteValueDictionary());

// Act
var match = matcher.Match(new PathString(path));
Expand Down

0 comments on commit 226cfb1

Please sign in to comment.