From 337fcaeb1c65f620a0ded25809484b7088a5a9bd Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 21 Apr 2019 16:08:07 -0700 Subject: [PATCH 1/3] Avoid allocating the CandidateSet when there's a single match - Add fast path for 0 route values, and policies --- src/Http/Routing/src/Matching/DfaMatcher.cs | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs index b1dfcb36dc33..c7ba54b03313 100644 --- a/src/Http/Routing/src/Matching/DfaMatcher.cs +++ b/src/Http/Routing/src/Matching/DfaMatcher.cs @@ -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. using System; @@ -53,7 +53,8 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector // FindCandidateSet will process the DFA and return a candidate set. This does // some preliminary matching of the URL (mostly the literal segments). var (candidates, policies) = FindCandidateSet(httpContext, path, segments); - if (candidates.Length == 0) + var candidateCount = candidates.Length; + if (candidateCount == 0) { if (log) { @@ -68,6 +69,26 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector Logger.CandidatesFound(_logger, path, candidates); } + var policyCount = policies.Length; + + // This is a fast path for single candidate, 0 policies and default selector + if (policyCount == 0 && candidateCount == 1 && _selector is DefaultEndpointSelector) + { + ref var candidate = ref candidates[0]; + var flags = candidate.Flags; + + // Just strict path matching + if (flags == Candidate.CandidateFlags.None) + { + // TODO: Remove this allocation + context.RouteValues = new RouteValueDictionary(); + 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 // priority order. // @@ -83,7 +104,7 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector // `candidateSet` is the mutable state that we pass to the EndpointSelector. 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. // @@ -165,7 +186,7 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector } } - if (policies.Length == 0) + if (policyCount == 0) { // Perf: avoid a state machine if there are no polices return _selector.SelectAsync(httpContext, context, candidateSet); From ae20d991a9e9c49c4d5380680ed845fd0777716b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 21 Apr 2019 16:26:49 -0700 Subject: [PATCH 2/3] Remove RVD allocation in the fast path --- src/Http/Routing/src/EndpointSelectorContext.cs | 6 +++--- src/Http/Routing/src/Matching/DfaMatcher.cs | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Http/Routing/src/EndpointSelectorContext.cs b/src/Http/Routing/src/EndpointSelectorContext.cs index 848cc2736649..1aed4f22276b 100644 --- a/src/Http/Routing/src/EndpointSelectorContext.cs +++ b/src/Http/Routing/src/EndpointSelectorContext.cs @@ -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. using System; @@ -24,7 +24,7 @@ public sealed class EndpointSelectorContext : IEndpointFeature, IRouteValuesFeat /// public RouteValueDictionary RouteValues { - get => _routeValues; + get => _routeValues ?? (_routeValues = new RouteValueDictionary()); set { _routeValues = value; @@ -67,4 +67,4 @@ RouteData IRoutingFeature.RouteData set => throw new NotSupportedException(); } } -} \ No newline at end of file +} diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs index c7ba54b03313..2f19a4395af4 100644 --- a/src/Http/Routing/src/Matching/DfaMatcher.cs +++ b/src/Http/Routing/src/Matching/DfaMatcher.cs @@ -80,8 +80,6 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector // Just strict path matching if (flags == Candidate.CandidateFlags.None) { - // TODO: Remove this allocation - context.RouteValues = new RouteValueDictionary(); context.Endpoint = candidate.Endpoint; // We're done From cd3d266424412b0be66714803124b161d1504b32 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 21 Apr 2019 19:18:19 -0700 Subject: [PATCH 3/3] PR feedback --- src/Http/Routing/src/Matching/DfaMatcher.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs index 2f19a4395af4..036351760a0e 100644 --- a/src/Http/Routing/src/Matching/DfaMatcher.cs +++ b/src/Http/Routing/src/Matching/DfaMatcher.cs @@ -16,6 +16,7 @@ internal sealed class DfaMatcher : Matcher private readonly EndpointSelector _selector; private readonly DfaState[] _states; private readonly int _maxSegmentCount; + private readonly bool _isDefaultEndpointSelector; public DfaMatcher(ILogger logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount) { @@ -23,6 +24,7 @@ public DfaMatcher(ILogger logger, EndpointSelector selector, DfaStat _selector = selector; _states = states; _maxSegmentCount = maxSegmentCount; + _isDefaultEndpointSelector = selector is DefaultEndpointSelector; } public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) @@ -72,13 +74,12 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector var policyCount = policies.Length; // This is a fast path for single candidate, 0 policies and default selector - if (policyCount == 0 && candidateCount == 1 && _selector is DefaultEndpointSelector) + if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector) { ref var candidate = ref candidates[0]; - var flags = candidate.Flags; // Just strict path matching - if (flags == Candidate.CandidateFlags.None) + if (candidate.Flags == Candidate.CandidateFlags.None) { context.Endpoint = candidate.Endpoint;