diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBuilderMultipleEntryBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBuilderMultipleEntryBenchmark.cs index 002118caca..23f1bf0729 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBuilderMultipleEntryBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matching/MatcherBuilderMultipleEntryBenchmark.cs @@ -2,14 +2,25 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Threading.Tasks; using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Routing.Matching { public partial class MatcherBuilderMultipleEntryBenchmark : EndpointRoutingBenchmarkBase { private IServiceProvider _services; + private List _policies; + private ILoggerFactory _loggerFactory; + private DefaultEndpointSelector _selector; + private DefaultParameterPolicyFactory _parameterPolicyFactory; [GlobalSetup] public void Setup() @@ -28,6 +39,26 @@ namespace Microsoft.AspNetCore.Routing.Matching Endpoints[8] = CreateEndpoint("/v2/account/{id}", "POST"); Endpoints[9] = CreateEndpoint("/v2/account/{id}", "UPDATE"); + // Define an unordered mixture of policies that implement INodeBuilderPolicy, + // IEndpointComparerPolicy and/or IEndpointSelectorPolicy + _policies = new List() + { + CreateNodeBuilderPolicy(4), + CreateUberPolicy(2), + CreateNodeBuilderPolicy(3), + CreateEndpointComparerPolicy(5), + CreateNodeBuilderPolicy(1), + CreateEndpointSelectorPolicy(9), + CreateEndpointComparerPolicy(7), + CreateNodeBuilderPolicy(6), + CreateEndpointSelectorPolicy(10), + CreateUberPolicy(12), + CreateEndpointComparerPolicy(11) + }; + _loggerFactory = NullLoggerFactory.Instance; + _selector = new DefaultEndpointSelector(); + _parameterPolicyFactory = new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), new TestServiceProvider()); + _services = CreateServices(); } @@ -46,5 +77,132 @@ namespace Microsoft.AspNetCore.Routing.Matching var builder = _services.GetRequiredService(); SetupMatcher(builder); } + + [Benchmark] + public void Constructor_Policies() + { + new DfaMatcherBuilder(_loggerFactory, _parameterPolicyFactory, _selector, _policies); + } + + private static MatcherPolicy CreateNodeBuilderPolicy(int order) + { + return new TestNodeBuilderPolicy(order); + } + private static MatcherPolicy CreateEndpointComparerPolicy(int order) + { + return new TestEndpointComparerPolicy(order); + } + + private static MatcherPolicy CreateEndpointSelectorPolicy(int order) + { + return new TestEndpointSelectorPolicy(order); + } + + private static MatcherPolicy CreateUberPolicy(int order) + { + return new TestUberPolicy(order); + } + + private class TestUberPolicy : TestMatcherPolicyBase, INodeBuilderPolicy, IEndpointComparerPolicy + { + public TestUberPolicy(int order) : base(order) + { + } + + public IComparer Comparer => new TestEndpointComparer(); + + public bool AppliesToEndpoints(IReadOnlyList endpoints) + { + return false; + } + + public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList edges) + { + throw new NotImplementedException(); + } + + public IReadOnlyList GetEdges(IReadOnlyList endpoints) + { + throw new NotImplementedException(); + } + } + + private class TestNodeBuilderPolicy : TestMatcherPolicyBase, INodeBuilderPolicy + { + public TestNodeBuilderPolicy(int order) : base(order) + { + } + + public bool AppliesToEndpoints(IReadOnlyList endpoints) + { + return false; + } + + public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList edges) + { + throw new NotImplementedException(); + } + + public IReadOnlyList GetEdges(IReadOnlyList endpoints) + { + throw new NotImplementedException(); + } + } + + private class TestEndpointComparerPolicy : TestMatcherPolicyBase, IEndpointComparerPolicy + { + public TestEndpointComparerPolicy(int order) : base(order) + { + } + + public IComparer Comparer => new TestEndpointComparer(); + + public bool AppliesToEndpoints(IReadOnlyList endpoints) + { + return false; + } + + public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates) + { + throw new NotImplementedException(); + } + } + + private class TestEndpointSelectorPolicy : TestMatcherPolicyBase, IEndpointSelectorPolicy + { + public TestEndpointSelectorPolicy(int order) : base(order) + { + } + + public bool AppliesToEndpoints(IReadOnlyList endpoints) + { + return false; + } + + public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidates) + { + throw new NotImplementedException(); + } + } + + private abstract class TestMatcherPolicyBase : MatcherPolicy + { + private int _order; + + protected TestMatcherPolicyBase(int order) + { + _order = order; + } + + public override int Order { get { return _order; } } + } + + private class TestEndpointComparer : IComparer + { + public int Compare(Endpoint x, Endpoint y) + { + return 0; + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing/Matching/DfaMatcherBuilder.cs b/src/Microsoft.AspNetCore.Routing/Matching/DfaMatcherBuilder.cs index b4e9ff538d..1b2172db53 100644 --- a/src/Microsoft.AspNetCore.Routing/Matching/DfaMatcherBuilder.cs +++ b/src/Microsoft.AspNetCore.Routing/Matching/DfaMatcherBuilder.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matching private readonly ILoggerFactory _loggerFactory; private readonly ParameterPolicyFactory _parameterPolicyFactory; private readonly EndpointSelector _selector; - private readonly MatcherPolicy[] _policies; + private readonly IEndpointSelectorPolicy[] _endpointSelectorPolicies; private readonly INodeBuilderPolicy[] _nodeBuilders; private readonly EndpointComparer _comparer; @@ -39,11 +39,11 @@ namespace Microsoft.AspNetCore.Routing.Matching _loggerFactory = loggerFactory; _parameterPolicyFactory = parameterPolicyFactory; _selector = selector; - _policies = policies.OrderBy(p => p.Order).ToArray(); - // Taking care to use _policies, which has been sorted. - _nodeBuilders = _policies.OfType().ToArray(); - _comparer = new EndpointComparer(_policies.OfType().ToArray()); + var (nodeBuilderPolicies, endpointComparerPolicies, endpointSelectorPolicies) = ExtractPolicies(policies.OrderBy(p => p.Order)); + _endpointSelectorPolicies = endpointSelectorPolicies; + _nodeBuilders = nodeBuilderPolicies; + _comparer = new EndpointComparer(endpointComparerPolicies); _assignments = new Dictionary(StringComparer.OrdinalIgnoreCase); _slots = new List>(); @@ -383,10 +383,10 @@ namespace Microsoft.AspNetCore.Routing.Matching List endpointSelectorPolicies = null; if (node.Matches?.Count > 0) { - for (var i = 0; i < _policies.Length; i++) + for (var i = 0; i < _endpointSelectorPolicies.Length; i++) { - if (_policies[i] is IEndpointSelectorPolicy endpointSelectorPolicy && - endpointSelectorPolicy.AppliesToEndpoints(node.Matches)) + var endpointSelectorPolicy = _endpointSelectorPolicies[i]; + if (endpointSelectorPolicy.AppliesToEndpoints(node.Matches)) { if (endpointSelectorPolicies == null) { @@ -465,16 +465,16 @@ namespace Microsoft.AspNetCore.Routing.Matching // internal for tests internal Candidate CreateCandidate(Endpoint endpoint, int score) { - _assignments.Clear(); - _slots.Clear(); - _captures.Clear(); - _complexSegments.Clear(); - _constraints.Clear(); - (string parameterName, int segmentIndex, int slotIndex) catchAll = default; if (endpoint is RouteEndpoint routeEndpoint) { + _assignments.Clear(); + _slots.Clear(); + _captures.Clear(); + _complexSegments.Clear(); + _constraints.Clear(); + foreach (var kvp in routeEndpoint.RoutePattern.Defaults) { _assignments.Add(kvp.Key, _assignments.Count); @@ -539,16 +539,27 @@ namespace Microsoft.AspNetCore.Routing.Matching } } } - } - return new Candidate( - endpoint, - score, - _slots.ToArray(), - _captures.ToArray(), - catchAll, - _complexSegments.ToArray(), - _constraints.ToArray()); + return new Candidate( + endpoint, + score, + _slots.ToArray(), + _captures.ToArray(), + catchAll, + _complexSegments.ToArray(), + _constraints.ToArray()); + } + else + { + return new Candidate( + endpoint, + score, + Array.Empty>(), + Array.Empty<(string parameterName, int segmentIndex, int slotIndex)>(), + catchAll, + Array.Empty<(RoutePatternPathSegment pathSegment, int segmentIndex)>(), + Array.Empty>()); + } } private int[] GetGroupLengths(DfaNode node) @@ -682,5 +693,32 @@ namespace Microsoft.AspNetCore.Routing.Matching work = nextWork; } } + + private static (INodeBuilderPolicy[] nodeBuilderPolicies, IEndpointComparerPolicy[] endpointComparerPolicies, IEndpointSelectorPolicy[] endpointSelectorPolicies) ExtractPolicies(IEnumerable policies) + { + var nodeBuilderPolicies = new List(); + var endpointComparerPolicies = new List(); + var endpointSelectorPolicies = new List(); + + foreach (var policy in policies) + { + if (policy is INodeBuilderPolicy nodeBuilderPolicy) + { + nodeBuilderPolicies.Add(nodeBuilderPolicy); + } + + if (policy is IEndpointComparerPolicy endpointComparerPolicy) + { + endpointComparerPolicies.Add(endpointComparerPolicy); + } + + if (policy is IEndpointSelectorPolicy endpointSelectorPolicy) + { + endpointSelectorPolicies.Add(endpointSelectorPolicy); + } + } + + return (nodeBuilderPolicies.ToArray(), endpointComparerPolicies.ToArray(), endpointSelectorPolicies.ToArray()); + } } }