Skip to content

Commit d62d33c

Browse files
authored
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
1 parent 418f3d8 commit d62d33c

File tree

2 files changed

+27
-7
lines changed

2 files changed

+27
-7
lines changed

src/Http/Routing/src/EndpointSelectorContext.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -24,7 +24,7 @@ public sealed class EndpointSelectorContext : IEndpointFeature, IRouteValuesFeat
2424
/// </summary>
2525
public RouteValueDictionary RouteValues
2626
{
27-
get => _routeValues;
27+
get => _routeValues ?? (_routeValues = new RouteValueDictionary());
2828
set
2929
{
3030
_routeValues = value;
@@ -67,4 +67,4 @@ RouteData IRoutingFeature.RouteData
6767
set => throw new NotSupportedException();
6868
}
6969
}
70-
}
70+
}

src/Http/Routing/src/Matching/DfaMatcher.cs

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -16,13 +16,15 @@ internal sealed class DfaMatcher : Matcher
1616
private readonly EndpointSelector _selector;
1717
private readonly DfaState[] _states;
1818
private readonly int _maxSegmentCount;
19+
private readonly bool _isDefaultEndpointSelector;
1920

2021
public DfaMatcher(ILogger<DfaMatcher> logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount)
2122
{
2223
_logger = logger;
2324
_selector = selector;
2425
_states = states;
2526
_maxSegmentCount = maxSegmentCount;
27+
_isDefaultEndpointSelector = selector is DefaultEndpointSelector;
2628
}
2729

2830
public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context)
@@ -53,7 +55,8 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector
5355
// FindCandidateSet will process the DFA and return a candidate set. This does
5456
// some preliminary matching of the URL (mostly the literal segments).
5557
var (candidates, policies) = FindCandidateSet(httpContext, path, segments);
56-
if (candidates.Length == 0)
58+
var candidateCount = candidates.Length;
59+
if (candidateCount == 0)
5760
{
5861
if (log)
5962
{
@@ -68,6 +71,23 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector
6871
Logger.CandidatesFound(_logger, path, candidates);
6972
}
7073

74+
var policyCount = policies.Length;
75+
76+
// This is a fast path for single candidate, 0 policies and default selector
77+
if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector)
78+
{
79+
ref var candidate = ref candidates[0];
80+
81+
// Just strict path matching
82+
if (candidate.Flags == Candidate.CandidateFlags.None)
83+
{
84+
context.Endpoint = candidate.Endpoint;
85+
86+
// We're done
87+
return Task.CompletedTask;
88+
}
89+
}
90+
7191
// At this point we have a candidate set, defined as a list of endpoints in
7292
// priority order.
7393
//
@@ -83,7 +103,7 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector
83103
// `candidateSet` is the mutable state that we pass to the EndpointSelector.
84104
var candidateSet = new CandidateSet(candidates);
85105

86-
for (var i = 0; i < candidates.Length; i++)
106+
for (var i = 0; i < candidateCount; i++)
87107
{
88108
// PERF: using ref here to avoid copying around big structs.
89109
//
@@ -165,7 +185,7 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector
165185
}
166186
}
167187

168-
if (policies.Length == 0)
188+
if (policyCount == 0)
169189
{
170190
// Perf: avoid a state machine if there are no polices
171191
return _selector.SelectAsync(httpContext, context, candidateSet);

0 commit comments

Comments
 (0)