Avoid allocating the CandidateSet when there's a single match (#9622)

- Add fast path for 0 route values, and policies
- Remove RVD allocation in the fast path
This commit is contained in:
David Fowler 2019-04-21 23:33:40 -07:00 committed by GitHub
parent 418f3d82dd
commit d62d33c531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 27 additions and 7 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Routing
/// </summary> /// </summary>
public RouteValueDictionary RouteValues public RouteValueDictionary RouteValues
{ {
get => _routeValues; get => _routeValues ?? (_routeValues = new RouteValueDictionary());
set set
{ {
_routeValues = value; _routeValues = value;
@ -67,4 +67,4 @@ namespace Microsoft.AspNetCore.Routing
set => throw new NotSupportedException(); set => throw new NotSupportedException();
} }
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
private readonly EndpointSelector _selector; private readonly EndpointSelector _selector;
private readonly DfaState[] _states; private readonly DfaState[] _states;
private readonly int _maxSegmentCount; private readonly int _maxSegmentCount;
private readonly bool _isDefaultEndpointSelector;
public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount) public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount)
{ {
@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
_selector = selector; _selector = selector;
_states = states; _states = states;
_maxSegmentCount = maxSegmentCount; _maxSegmentCount = maxSegmentCount;
_isDefaultEndpointSelector = selector is DefaultEndpointSelector;
} }
public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context)
@ -53,7 +55,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
// FindCandidateSet will process the DFA and return a candidate set. This does // FindCandidateSet will process the DFA and return a candidate set. This does
// some preliminary matching of the URL (mostly the literal segments). // some preliminary matching of the URL (mostly the literal segments).
var (candidates, policies) = FindCandidateSet(httpContext, path, segments); var (candidates, policies) = FindCandidateSet(httpContext, path, segments);
if (candidates.Length == 0) var candidateCount = candidates.Length;
if (candidateCount == 0)
{ {
if (log) if (log)
{ {
@ -68,6 +71,23 @@ namespace Microsoft.AspNetCore.Routing.Matching
Logger.CandidatesFound(_logger, path, candidates); Logger.CandidatesFound(_logger, path, candidates);
} }
var policyCount = policies.Length;
// This is a fast path for single candidate, 0 policies and default selector
if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector)
{
ref var candidate = ref candidates[0];
// Just strict path matching
if (candidate.Flags == Candidate.CandidateFlags.None)
{
context.Endpoint = candidate.Endpoint;
// We're done
return Task.CompletedTask;
}
}
// At this point we have a candidate set, defined as a list of endpoints in // At this point we have a candidate set, defined as a list of endpoints in
// priority order. // priority order.
// //
@ -83,7 +103,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
// `candidateSet` is the mutable state that we pass to the EndpointSelector. // `candidateSet` is the mutable state that we pass to the EndpointSelector.
var candidateSet = new CandidateSet(candidates); var candidateSet = new CandidateSet(candidates);
for (var i = 0; i < candidates.Length; i++) for (var i = 0; i < candidateCount; i++)
{ {
// PERF: using ref here to avoid copying around big structs. // PERF: using ref here to avoid copying around big structs.
// //
@ -165,7 +185,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
} }
} }
if (policies.Length == 0) if (policyCount == 0)
{ {
// Perf: avoid a state machine if there are no polices // Perf: avoid a state machine if there are no polices
return _selector.SelectAsync(httpContext, context, candidateSet); return _selector.SelectAsync(httpContext, context, candidateSet);