diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/AzureMatcherBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/AzureMatcherBenchmark.cs index 5a5d9f0a44..1420ef4b5e 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/AzureMatcherBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/AzureMatcherBenchmark.cs @@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers private BarebonesMatcher _baseline; private Matcher _dfa; - private Matcher _instruction; private Matcher _route; private Matcher _tree; @@ -34,7 +33,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder()); _dfa = SetupMatcher(new DfaMatcherBuilder()); - _instruction = SetupMatcher(new InstructionMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder()); @@ -67,19 +65,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers } } - [Benchmark(OperationsPerInvoke = SampleCount)] - public async Task Instruction() - { - var feature = _feature; - for (var i = 0; i < SampleCount; i++) - { - var sample = _samples[i]; - var httpContext = _requests[sample]; - await _instruction.MatchAsync(httpContext, feature); - Validate(httpContext, _endpoints[sample], feature.Endpoint); - } - } - [Benchmark(OperationsPerInvoke = SampleCount)] public async Task LegacyRoute() { diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/GithubMatcherBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/GithubMatcherBenchmark.cs index 39f2fb3c45..58b4906006 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/GithubMatcherBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/GithubMatcherBenchmark.cs @@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers { private BarebonesMatcher _baseline; private Matcher _dfa; - private Matcher _instruction; private Matcher _route; private Matcher _tree; @@ -28,7 +27,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers _baseline = (BarebonesMatcher)SetupMatcher(new BarebonesMatcherBuilder()); _dfa = SetupMatcher(new DfaMatcherBuilder()); - _instruction = SetupMatcher(new InstructionMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder()); @@ -59,18 +57,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers } } - [Benchmark(OperationsPerInvoke = EndpointCount)] - public async Task Instruction() - { - var feature = _feature; - for (var i = 0; i < EndpointCount; i++) - { - var httpContext = _requests[i]; - await _instruction.MatchAsync(httpContext, feature); - Validate(httpContext, _endpoints[i], feature.Endpoint); - } - } - [Benchmark(OperationsPerInvoke = EndpointCount)] public async Task LegacyRoute() { diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SingleEntryMatcherBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SingleEntryMatcherBenchmark.cs index 586eb45390..278597b30e 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SingleEntryMatcherBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SingleEntryMatcherBenchmark.cs @@ -11,7 +11,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers { private Matcher _baseline; private Matcher _dfa; - private Matcher _instruction; private Matcher _route; private Matcher _tree; @@ -30,7 +29,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers _baseline = SetupMatcher(new TrivialMatcherBuilder()); _dfa = SetupMatcher(new DfaMatcherBuilder()); - _instruction = SetupMatcher(new InstructionMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder()); @@ -59,14 +57,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers Validate(_requests[0], _endpoints[0], feature.Endpoint); } - [Benchmark] - public async Task Instruction() - { - var feature = _feature; - await _instruction.MatchAsync(_requests[0], feature); - Validate(_requests[0], _endpoints[0], feature.Endpoint); - } - [Benchmark] public async Task LegacyRoute() { diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SmallEntryCountLiteralMatcherBenchark.cs b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SmallEntryCountLiteralMatcherBenchark.cs index 5804246ca1..ed88e69693 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SmallEntryCountLiteralMatcherBenchark.cs +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Matchers/SmallEntryCountLiteralMatcherBenchark.cs @@ -11,7 +11,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers { private Matcher _baseline; private Matcher _dfa; - private Matcher _instruction; private Matcher _route; private Matcher _tree; @@ -26,7 +25,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers _baseline = SetupMatcher(new TrivialMatcherBuilder()); _dfa = SetupMatcher(new DfaMatcherBuilder()); - _instruction = SetupMatcher(new InstructionMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder()); @@ -89,14 +87,6 @@ namespace Microsoft.AspNetCore.Routing.Matchers Validate(_requests[0], _endpoints[9], feature.Endpoint); } - [Benchmark] - public async Task Instruction() - { - var feature = _feature; - await _instruction.MatchAsync(_requests[0], feature); - Validate(_requests[0], _endpoints[9], feature.Endpoint); - } - [Benchmark] public async Task LegacyRoute() { diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj index 8c807811b5..145966eafb 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj @@ -33,12 +33,6 @@ Matchers\DfaMatcherBuilder.cs - - Matchers\InstructionMatcher.cs - - - Matchers\InstructionMatcherBuilder.cs - Matchers\RouteMatcher.cs diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcher.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcher.cs deleted file mode 100644 index cc4d9e6f8d..0000000000 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcher.cs +++ /dev/null @@ -1,214 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Routing.Matchers -{ - internal class InstructionMatcher : Matcher - { - private State _state; - - public InstructionMatcher(Instruction[] instructions, Candidate[] candidates, JumpTable[] tables) - { - _state = new State() - { - Instructions = instructions, - Candidates = candidates, - Tables = tables, - }; - } - - public unsafe override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature) - { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (feature == null) - { - throw new ArgumentNullException(nameof(feature)); - } - - var state = _state; - - var path = httpContext.Request.Path.Value; - var buffer = stackalloc PathSegment[32]; - var count = FastPathTokenizer.Tokenize(path, buffer, 32); - - var i = 0; - var candidates = new List(); - while (i < state.Instructions.Length) - { - var instruction = state.Instructions[i]; - switch (instruction.Code) - { - case InstructionCode.Accept: - { - if (count == instruction.Depth) - { - candidates.Add(state.Candidates[instruction.Payload]); - } - i++; - break; - } - case InstructionCode.Branch: - { - var table = state.Tables[instruction.Payload]; - i = table.GetDestination(buffer, count, path); - break; - } - case InstructionCode.Jump: - { - i = instruction.Payload; - break; - } - } - } - - var matches = new List<(Endpoint, RouteValueDictionary)>(); - for (i = 0; i < candidates.Count; i++) - { - var values = new RouteValueDictionary(); - var parameters = candidates[i].Parameters; - if (parameters != null) - { - for (var j = 0; j < parameters.Length; j++) - { - var parameter = parameters[j]; - if (parameter != null && buffer[j].Length == 0) - { - goto notmatch; - } - else if (parameter != null) - { - var value = path.Substring(buffer[j].Start, buffer[j].Length); - values.Add(parameter, value); - } - } - } - - matches.Add((candidates[i].Endpoint, values)); - - notmatch:; - } - - feature.Endpoint = matches.Count == 0 ? null : matches[0].Item1; - feature.Values = matches.Count == 0 ? null : matches[0].Item2; - - return Task.CompletedTask; - } - - public struct Candidate - { - public Endpoint Endpoint; - public string[] Parameters; - } - - public class State - { - public Candidate[] Candidates; - public Instruction[] Instructions; - public JumpTable[] Tables; - } - - [DebuggerDisplay("{ToDebugString(),nq}")] - [StructLayout(LayoutKind.Explicit)] - public struct Instruction - { - [FieldOffset(0)] - public byte Depth; - - [FieldOffset(3)] - public InstructionCode Code; - - [FieldOffset(4)] - public int Payload; - - private string ToDebugString() - { - return $"{Code}: {Payload}"; - } - } - - public enum InstructionCode : byte - { - Accept, - Branch, - Jump, - Pop, // Only used during the instruction builder phase - } - - public abstract class JumpTable - { - public unsafe abstract int GetDestination(PathSegment* segments, int depth, string path); - } - - public class JumpTableBuilder - { - private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>(); - - public int Depth { get; set; } - - public int Exit { get; set; } - - public void AddEntry(string text, int destination) - { - _entries.Add((text, destination)); - } - - public JumpTable Build() - { - return new SimpleJumpTable(Depth, Exit, _entries.ToArray()); - } - } - - public class SimpleJumpTable : JumpTable - { - private readonly (string text, int destination)[] _entries; - private readonly int _depth; - private readonly int _exit; - - public SimpleJumpTable(int depth, int exit, (string text, int destination)[] entries) - { - _depth = depth; - _exit = exit; - _entries = entries; - } - - public unsafe override int GetDestination(PathSegment* segments, int count, string path) - { - if (_depth == count) - { - return _exit; - } - - var start = segments[_depth].Start; - var length = segments[_depth].Length; - - for (var i = 0; i < _entries.Length; i++) - { - if (length == _entries[i].text.Length && - string.Compare( - path, - start, - _entries[i].text, - 0, - length, - StringComparison.OrdinalIgnoreCase) == 0) - { - return _entries[i].destination; - } - } - - return _exit; - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcherBuilder.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcherBuilder.cs deleted file mode 100644 index 40476d5008..0000000000 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcherBuilder.cs +++ /dev/null @@ -1,356 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Routing.Template; -using static Microsoft.AspNetCore.Routing.Matchers.InstructionMatcher; - -namespace Microsoft.AspNetCore.Routing.Matchers -{ - internal class InstructionMatcherBuilder : MatcherBuilder - { - private List _entries = new List(); - - public override void AddEndpoint(MatcherEndpoint endpoint) - { - var parsed = TemplateParser.Parse(endpoint.Template); - _entries.Add(new Entry() - { - Order = 0, - Pattern = parsed, - Precedence = RoutePrecedence.ComputeInbound(parsed), - Endpoint = endpoint, - }); - } - - public override Matcher Build() - { - _entries.Sort((x, y) => - { - var comparison = x.Order.CompareTo(y.Order); - if (comparison != 0) - { - return comparison; - } - - comparison = x.Precedence.CompareTo(y.Precedence); - if (comparison != 0) - { - return comparison; - } - - return x.Pattern.TemplateText.CompareTo(y.Pattern.TemplateText); - }); - - var roots = new List(); - - for (var i = 0; i < _entries.Count; i++) - { - var entry = _entries[i]; - - var parent = (SequenceNode)GetOrCreateRootNode(roots, entry.Order); - - var depth = 0; - for (; depth < entry.Pattern.Segments.Count; depth++) - { - var segment = entry.Pattern.Segments[depth]; - if (segment.IsSimple && segment.Parts[0].IsLiteral) - { - var branch = parent.GetNode() ?? parent.AddNode(new BranchNode(depth)); - - var index = -1; - for (var j = 0; j < branch.Literals.Count; j++) - { - if (string.Equals(segment.Parts[0].Text, branch.Literals[j], StringComparison.OrdinalIgnoreCase)) - { - index = j; - break; - } - } - - if (index == -1) - { - branch.Literals.Add(segment.Parts[0].Text); - branch.AddNode(new SequenceNode(depth + 1)); - index = branch.Children.Count - 1; - } - - parent = (SequenceNode)branch.Children[index]; - } - else if (segment.IsSimple && segment.Parts[0].IsParameter) - { - var parameter = parent.GetNode() ?? parent.AddNode(new ParameterNode(depth)); - if (parameter.Children.Count == 0) - { - parameter.AddNode(new SequenceNode(depth + 1)); - } - - parent = (SequenceNode)parameter.Children[0]; - } - else - { - throw new InvalidOperationException("Not implemented!"); - } - } - - parent.AddNode(new AcceptNode(depth, entry.Endpoint)); - } - - var builder = new InstructionBuilder(); - for (var i = 0; i < roots.Count; i++) - { - roots[i].Lower(builder); - } - - var (instructions, endpoints, tables) = builder; - var candidates = new Candidate[endpoints.Length]; - for (var i = 0; i < endpoints.Length; i++) - { - candidates[i] = CreateCandidate(endpoints[i]); - } - - return new InstructionMatcher(instructions, candidates, tables); - } - - private OrderNode GetOrCreateRootNode(List roots, int order) - { - OrderNode root = null; - for (var j = 0; j < roots.Count; j++) - { - if (roots[j].Order == order) - { - root = roots[j]; - break; - } - } - - if (root == null) - { - // Nodes are guaranteed to be in order because the entries are in order. - root = new OrderNode(order); - roots.Add(root); - } - - return root; - } - - private static Candidate CreateCandidate(MatcherEndpoint endpoint) - { - var parsed = TemplateParser.Parse(endpoint.Template); - return new Candidate() - { - Endpoint = endpoint, - Parameters = parsed.Segments.Select(s => s.IsSimple && s.Parts[0].IsParameter ? s.Parts[0].Name : null).ToArray(), - }; - } - - private class Entry - { - public int Order; - public decimal Precedence; - public RouteTemplate Pattern; - public MatcherEndpoint Endpoint; - } - - private class InstructionBuilder - { - private readonly List _instructions = new List(); - private readonly List _endpoints = new List(); - private readonly List _tables = new List(); - - private readonly List _blocks = new List(); - - public int Next => _instructions.Count; - - public void BeginBlock() - { - _blocks.Add(Next); - } - - public void EndBlock() - { - var start = _blocks[_blocks.Count - 1]; - var end = Next; - for (var i = start; i < end; i++) - { - if (_instructions[i].Code == InstructionCode.Pop) - { - _instructions[i] = new Instruction() - { - Code = InstructionCode.Jump, - Depth = _instructions[i].Depth, - Payload = end, - }; - } - } - - _blocks.RemoveAt(_blocks.Count - 1); - } - - public int AddInstruction(Instruction instruction) - { - _instructions.Add(instruction); - return _instructions.Count - 1; - } - - public int AddEndpoint(MatcherEndpoint endpoint) - { - _endpoints.Add(endpoint); - return _endpoints.Count - 1; - } - - public int AddJumpTable(JumpTableBuilder table) - { - _tables.Add(table); - return _tables.Count - 1; - } - - public void Deconstruct( - out Instruction[] instructions, - out MatcherEndpoint[] endpoints, - out JumpTable[] tables) - { - instructions = _instructions.ToArray(); - endpoints = _endpoints.ToArray(); - - tables = new JumpTable[_tables.Count]; - for (var i = 0; i < _tables.Count; i++) - { - tables[i] = _tables[i].Build(); - } - } - } - - private abstract class Node - { - public int Depth { get; protected set; } - public List Children { get; } = new List(); - - public abstract void Lower(InstructionBuilder builder); - - public TNode GetNode() where TNode : Node - { - for (var i = 0; i < Children.Count; i++) - { - if (Children[i] is TNode match) - { - return match; - } - } - - return null; - } - - public TNode AddNode(TNode node) where TNode : Node - { - // We already ordered the routes into precedence order - Children.Add(node); - return node; - } - } - - private class SequenceNode : Node - { - public SequenceNode(int depth) - { - Depth = depth; - } - - public override void Lower(InstructionBuilder builder) - { - for (var i = 0; i < Children.Count; i++) - { - Children[i].Lower(builder); - } - } - } - - private class OrderNode : SequenceNode - { - public OrderNode(int order) - : base(0) - { - Order = order; - } - - public int Order { get; } - } - - private class BranchNode : Node - { - public BranchNode(int depth) - { - Depth = depth; - } - - public List Literals { get; } = new List(); - - public override void Lower(InstructionBuilder builder) - { - var table = new JumpTableBuilder() { Depth = Depth, }; - var index = builder.AddJumpTable(table); - builder.AddInstruction(new Instruction() - { - Code = InstructionCode.Branch, - Depth = (byte)Depth, - Payload = index - }); - - builder.BeginBlock(); - - for (var i = 0; i < Children.Count; i++) - { - table.AddEntry(Literals[i], builder.Next); - Children[i].Lower(builder); - builder.AddInstruction(new Instruction() - { - Code = InstructionCode.Pop, - Depth = (byte)Depth, - }); - } - - builder.EndBlock(); - table.Exit = builder.Next; - } - } - - private class ParameterNode : Node - { - public ParameterNode(int depth) - { - Depth = depth; - } - - public override void Lower(InstructionBuilder builder) - { - for (var i = 0; i < Children.Count; i++) - { - Children[i].Lower(builder); - } - } - } - - private class AcceptNode : Node - { - public AcceptNode(int depth, MatcherEndpoint endpoint) - { - Depth = depth; - Endpoint = endpoint; - } - - public MatcherEndpoint Endpoint { get; } - - public override void Lower(InstructionBuilder builder) - { - builder.AddInstruction(new Instruction() - { - Code = InstructionCode.Accept, - Depth = (byte)Depth, - Payload = builder.AddEndpoint(Endpoint), - }); - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcherConformanceTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcherConformanceTest.cs deleted file mode 100644 index 65a902ff58..0000000000 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matchers/InstructionMatcherConformanceTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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. - -namespace Microsoft.AspNetCore.Routing.Matchers -{ - public class InstructionMatcherConformanceTest : MatcherConformanceTest - { - internal override Matcher CreateMatcher(params MatcherEndpoint[] endpoints) - { - var builder = new InstructionMatcherBuilder(); - for (int i = 0; i < endpoints.Length; i++) - { - builder.AddEndpoint(endpoints[i]); - } - return builder.Build(); - } - } -}