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

View File

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