Reuse collections in DfaMatcherBuilder (#778)

This commit is contained in:
James Newton-King 2018-09-06 12:25:45 +12:00 committed by GitHub
parent e5c520b4ca
commit a777a4cdd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 24 deletions

View File

@ -19,6 +19,13 @@ namespace Microsoft.AspNetCore.Routing.Matching
private readonly INodeBuilderPolicy[] _nodeBuilders;
private readonly EndpointComparer _comparer;
// These collections are reused when building candidates
private readonly Dictionary<string, int> _assignments;
private readonly List<KeyValuePair<string, object>> _slots;
private readonly List<(string parameterName, int segmentIndex, int slotIndex)> _captures;
private readonly List<(RoutePatternPathSegment pathSegment, int segmentIndex)> _complexSegments;
private readonly List<KeyValuePair<string, IRouteConstraint>> _constraints;
public DfaMatcherBuilder(
ParameterPolicyFactory parameterPolicyFactory,
EndpointSelector selector,
@ -31,6 +38,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
// Taking care to use _policies, which has been sorted.
_nodeBuilders = _policies.OfType<INodeBuilderPolicy>().ToArray();
_comparer = new EndpointComparer(_policies.OfType<IEndpointComparerPolicy>().ToArray());
_assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
_slots = new List<KeyValuePair<string, object>>();
_captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
_complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
_constraints = new List<KeyValuePair<string, IRouteConstraint>>();
}
public override void AddEndpoint(RouteEndpoint endpoint)
@ -50,6 +63,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
// this list will hold the set of items we need to process at the current
// stage.
var work = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>();
List<(RouteEndpoint endpoint, List<DfaNode> parents)> previousWork = null;
var root = new DfaNode() { PathDepth = 0, Label = "/" };
@ -63,14 +77,26 @@ namespace Microsoft.AspNetCore.Routing.Matching
work.Add((endpoint, new List<DfaNode>() { root, }));
}
var workCount = work.Count;
// Now we process the entries a level at a time.
for (var depth = 0; depth <= maxDepth; depth++)
{
// As we process items, collect the next set of items.
var nextWork = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>();
List<(RouteEndpoint endpoint, List<DfaNode> parents)> nextWork;
var nextWorkCount = 0;
if (previousWork == null)
{
nextWork = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>();
}
else
{
// Reuse previous collection for the next collection
// Don't clear the list so nested lists can be reused
nextWork = previousWork;
}
for (var i = 0; i < work.Count; i++)
for (var i = 0; i < workCount; i++)
{
var (endpoint, parents) = work[i];
@ -84,7 +110,23 @@ namespace Microsoft.AspNetCore.Routing.Matching
}
// Find the parents of this edge at the current depth
var nextParents = new List<DfaNode>();
List<DfaNode> nextParents;
if (nextWorkCount < nextWork.Count)
{
nextParents = nextWork[nextWorkCount].parents;
nextParents.Clear();
nextWork[nextWorkCount] = (endpoint, nextParents);
}
else
{
nextParents = new List<DfaNode>();
// Add to the next set of work now so the list will be reused
// even if there are no parents
nextWork.Add((endpoint, nextParents));
}
var segment = GetCurrentSegment(endpoint, depth);
if (segment == null)
{
@ -178,12 +220,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
if (nextParents.Count > 0)
{
nextWork.Add((endpoint, nextParents));
nextWorkCount++;
}
}
// Prepare the process the next stage.
previousWork = work;
work = nextWork;
workCount = nextWorkCount;
}
// Build the trees of policy nodes (like HTTP methods). Post-order traversal
@ -370,20 +414,20 @@ namespace Microsoft.AspNetCore.Routing.Matching
// internal for tests
internal Candidate CreateCandidate(Endpoint endpoint, int score)
{
var assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var slots = new List<KeyValuePair<string, object>>();
var captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
(string parameterName, int segmentIndex, int slotIndex) catchAll = default;
_assignments.Clear();
_slots.Clear();
_captures.Clear();
_complexSegments.Clear();
_constraints.Clear();
var complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
var constraints = new List<KeyValuePair<string, IRouteConstraint>>();
(string parameterName, int segmentIndex, int slotIndex) catchAll = default;
if (endpoint is RouteEndpoint routeEndpoint)
{
foreach (var kvp in routeEndpoint.RoutePattern.Defaults)
{
assignments.Add(kvp.Key, assignments.Count);
slots.Add(kvp);
_assignments.Add(kvp.Key, _assignments.Count);
_slots.Add(kvp);
}
for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
@ -400,13 +444,13 @@ namespace Microsoft.AspNetCore.Routing.Matching
continue;
}
if (!assignments.TryGetValue(parameterPart.Name, out var slotIndex))
if (!_assignments.TryGetValue(parameterPart.Name, out var slotIndex))
{
slotIndex = assignments.Count;
assignments.Add(parameterPart.Name, slotIndex);
slotIndex = _assignments.Count;
_assignments.Add(parameterPart.Name, slotIndex);
var hasDefaultValue = parameterPart.Default != null || parameterPart.IsCatchAll;
slots.Add(hasDefaultValue ? new KeyValuePair<string, object>(parameterPart.Name, parameterPart.Default) : default);
_slots.Add(hasDefaultValue ? new KeyValuePair<string, object>(parameterPart.Name, parameterPart.Default) : default);
}
if (parameterPart.IsCatchAll)
@ -415,7 +459,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
}
else
{
captures.Add((parameterPart.Name, i, slotIndex));
_captures.Add((parameterPart.Name, i, slotIndex));
}
}
@ -427,7 +471,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
continue;
}
complexSegments.Add((segment, i));
_complexSegments.Add((segment, i));
}
foreach (var kvp in routeEndpoint.RoutePattern.ParameterPolicies)
@ -440,7 +484,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
if (parameterPolicy is IRouteConstraint routeConstraint)
{
constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
_constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
}
}
}
@ -449,11 +493,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
return new Candidate(
endpoint,
score,
slots.ToArray(),
captures.ToArray(),
_slots.ToArray(),
_captures.ToArray(),
catchAll,
complexSegments.ToArray(),
constraints.ToArray());
_complexSegments.ToArray(),
_constraints.ToArray());
}
private int[] GetGroupLengths(DfaNode node)

View File

@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
public override int GetHashCode()
{
var hash = new HashCodeCombiner();
hash.Add(IsCorsPreflightRequest);
hash.Add(IsCorsPreflightRequest ? 1 : 0);
hash.Add(HttpMethod, StringComparer.Ordinal);
return hash;
}