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:
parent
418f3d82dd
commit
d62d33c531
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue