Tokenize Less

This commit is contained in:
Ryan Nowak 2018-08-14 10:07:22 -07:00
parent e2892f82ed
commit e09b88ebd6
8 changed files with 42 additions and 27 deletions

View File

@ -9,6 +9,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
// Generated from https://github.com/Azure/azure-rest-api-specs // Generated from https://github.com/Azure/azure-rest-api-specs
public partial class MatcherFindCandidateSetAzureBenchmark : MatcherBenchmarkBase public partial class MatcherFindCandidateSetAzureBenchmark : MatcherBenchmarkBase
{ {
// SegmentCount should be max-segments + 1, but we don't have a good way to compute
// it here, so using 16 as a safe guess.
private const int SegmentCount = 16;
private const int SampleCount = 100; private const int SampleCount = 100;
private BarebonesMatcher _baseline; private BarebonesMatcher _baseline;
@ -58,7 +62,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var httpContext = Requests[sample]; var httpContext = Requests[sample];
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
Span<PathSegment> segments = stackalloc PathSegment[FastPathTokenizer.DefaultSegmentCount]; Span<PathSegment> segments = stackalloc PathSegment[SegmentCount];
var count = FastPathTokenizer.Tokenize(path, segments); var count = FastPathTokenizer.Tokenize(path, segments);
var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count)); var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count));

View File

@ -11,6 +11,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
// Use https://editor2.swagger.io/ to convert from yaml to json- // Use https://editor2.swagger.io/ to convert from yaml to json-
public partial class MatcherFindCandidateSetGithubBenchmark : MatcherBenchmarkBase public partial class MatcherFindCandidateSetGithubBenchmark : MatcherBenchmarkBase
{ {
// SegmentCount should be max-segments + 1, but we don't have a good way to compute
// it here, so using 16 as a safe guess.
private const int SegmentCount = 16;
private BarebonesMatcher _baseline; private BarebonesMatcher _baseline;
private DfaMatcher _dfa; private DfaMatcher _dfa;
@ -50,7 +54,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var httpContext = Requests[i]; var httpContext = Requests[i];
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
Span<PathSegment> segments = stackalloc PathSegment[FastPathTokenizer.DefaultSegmentCount]; Span<PathSegment> segments = stackalloc PathSegment[SegmentCount];
var count = FastPathTokenizer.Tokenize(path, segments); var count = FastPathTokenizer.Tokenize(path, segments);
var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count)); var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count));

View File

@ -7,8 +7,11 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching namespace Microsoft.AspNetCore.Routing.Matching
{ {
public class MatcheFindCandidateSetSingleEntryBenchmark : MatcherBenchmarkBase public class MatcherFindCandidateSetSingleEntryBenchmark : MatcherBenchmarkBase
{ {
// SegmentCount should be max-segments + 1
private const int SegmentCount = 2;
private TrivialMatcher _baseline; private TrivialMatcher _baseline;
private DfaMatcher _dfa; private DfaMatcher _dfa;
@ -22,7 +25,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
Requests[0] = new DefaultHttpContext(); Requests[0] = new DefaultHttpContext();
Requests[0].RequestServices = CreateServices(); Requests[0].RequestServices = CreateServices();
Requests[0].Request.Path = "/plaintext"; Requests[0].Request.Path = "/plaintext";
_baseline = (TrivialMatcher)SetupMatcher(new TrivialMatcherBuilder()); _baseline = (TrivialMatcher)SetupMatcher(new TrivialMatcherBuilder());
_dfa = (DfaMatcher)SetupMatcher(CreateDfaMatcherBuilder()); _dfa = (DfaMatcher)SetupMatcher(CreateDfaMatcherBuilder());
} }
@ -51,7 +54,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
var httpContext = Requests[0]; var httpContext = Requests[0];
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
Span<PathSegment> segments = stackalloc PathSegment[FastPathTokenizer.DefaultSegmentCount]; Span<PathSegment> segments = stackalloc PathSegment[SegmentCount];
var count = FastPathTokenizer.Tokenize(path, segments); var count = FastPathTokenizer.Tokenize(path, segments);
var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count)); var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count));

View File

@ -9,6 +9,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
public class MatcherFindCandidateSetSmallEntryCountBenchmark : MatcherBenchmarkBase public class MatcherFindCandidateSetSmallEntryCountBenchmark : MatcherBenchmarkBase
{ {
// SegmentCount should be max-segments + 1
private const int SegmentCount = 6;
private TrivialMatcher _baseline; private TrivialMatcher _baseline;
private DfaMatcher _dfa; private DfaMatcher _dfa;
@ -87,7 +90,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var httpContext = Requests[0]; var httpContext = Requests[0];
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
Span<PathSegment> segments = stackalloc PathSegment[FastPathTokenizer.DefaultSegmentCount]; Span<PathSegment> segments = stackalloc PathSegment[SegmentCount];
var count = FastPathTokenizer.Tokenize(path, segments); var count = FastPathTokenizer.Tokenize(path, segments);
var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count)); var candidates = _dfa.FindCandidateSet(httpContext, path, segments.Slice(0, count));

View File

@ -13,11 +13,13 @@ 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;
public DfaMatcher(EndpointSelector selector, DfaState[] states)
public DfaMatcher(EndpointSelector selector, DfaState[] states, int maxSegmentCount)
{ {
_selector = selector; _selector = selector;
_states = states; _states = states;
_maxSegmentCount = maxSegmentCount;
} }
public sealed override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature) public sealed override Task MatchAsync(HttpContext httpContext, IEndpointFeature feature)
@ -38,7 +40,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
// First tokenize the path into series of segments. // First tokenize the path into series of segments.
Span<PathSegment> buffer = stackalloc PathSegment[FastPathTokenizer.DefaultSegmentCount]; Span<PathSegment> buffer = stackalloc PathSegment[_maxSegmentCount];
var count = FastPathTokenizer.Tokenize(path, buffer); var count = FastPathTokenizer.Tokenize(path, buffer);
var segments = buffer.Slice(0, count); var segments = buffer.Slice(0, count);

View File

@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
// stage. // stage.
var work = new List<(MatcherEndpoint endpoint, List<DfaNode> parents)>(); var work = new List<(MatcherEndpoint endpoint, List<DfaNode> parents)>();
var root = new DfaNode() { Depth = 0, Label = "/" }; var root = new DfaNode() { PathDepth = 0, Label = "/" };
// To prepare for this we need to compute the max depth, as well as // To prepare for this we need to compute the max depth, as well as
// a seed list of items to process (entry, root). // a seed list of items to process (entry, root).
@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
next = new DfaNode() next = new DfaNode()
{ {
Depth = parent.Depth + 1, PathDepth = parent.PathDepth + 1,
Label = parent.Label + literal + "/", Label = parent.Label + literal + "/",
}; };
parent.Literals.Add(literal, next); parent.Literals.Add(literal, next);
@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
parent.CatchAll = new DfaNode() parent.CatchAll = new DfaNode()
{ {
Depth = parent.Depth + 1, PathDepth = parent.PathDepth + 1,
Label = parent.Label + "{*...}/", Label = parent.Label + "{*...}/",
}; };
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
parent.Parameters = new DfaNode() parent.Parameters = new DfaNode()
{ {
Depth = parent.Depth + 1, PathDepth = parent.PathDepth + 1,
Label = parent.Label + "{...}/", Label = parent.Label + "{...}/",
}; };
} }
@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
parent.Parameters = new DfaNode() parent.Parameters = new DfaNode()
{ {
Depth = parent.Depth + 1, PathDepth = parent.PathDepth + 1,
Label = parent.Label + "{...}/", Label = parent.Label + "{...}/",
}; };
} }
@ -217,6 +217,13 @@ namespace Microsoft.AspNetCore.Routing.Matching
{ {
var root = BuildDfaTree(); var root = BuildDfaTree();
var maxSegmentCount = 0;
root.Visit((node) => maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth));
// The max segment count is the maximum path-node-depth +1. We need
// the +1 to capture any additional content after the 'last' segment.
maxSegmentCount++;
var states = new List<DfaState>(); var states = new List<DfaState>();
var tableBuilders = new List<(JumpTableBuilder pathBuilder, PolicyJumpTableBuilder policyBuilder)>(); var tableBuilders = new List<(JumpTableBuilder pathBuilder, PolicyJumpTableBuilder policyBuilder)>();
AddNode(root, states, tableBuilders); AddNode(root, states, tableBuilders);
@ -251,7 +258,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
tableBuilders[i].policyBuilder?.Build()); tableBuilders[i].policyBuilder?.Build());
} }
return new DfaMatcher(_selector, states.ToArray()); return new DfaMatcher(_selector, states.ToArray(), maxSegmentCount);
} }
private int AddNode( private int AddNode(

View File

@ -22,7 +22,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
// The depth of the node. The depth indicates the number of segments // The depth of the node. The depth indicates the number of segments
// that must be processed to arrive at this node. // that must be processed to arrive at this node.
public int Depth { get; set; } //
// This value is not computed for Policy nodes and will be set to -1.
public int PathDepth { get; set; } = -1;
// Just for diagnostics and debugging // Just for diagnostics and debugging
public string Label { get; set; } public string Label { get; set; }
@ -71,7 +73,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append(Label); builder.Append(Label);
builder.Append(" d:"); builder.Append(" d:");
builder.Append(Depth); builder.Append(PathDepth);
builder.Append(" m:"); builder.Append(" m:");
builder.Append(Matches.Count); builder.Append(Matches.Count);
builder.Append(" c: "); builder.Append(" c: ");

View File

@ -9,16 +9,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
// to PathTokenizer. // to PathTokenizer.
internal static class FastPathTokenizer internal static class FastPathTokenizer
{ {
// The default limit for the number of segments we tokenize.
//
// Historically the limit on the number of segments routing supports is 28.
// RoutePrecedence computes precedence based on a decimal, which supports 28
// or 29 digits.
//
// So setting this limit to 32 should work pretty well. We also expect the tokenizer
// to be used with stackalloc, so we want a small number.
public const int DefaultSegmentCount = 32;
// This section tokenizes the path by marking the sequence of slashes, and their // This section tokenizes the path by marking the sequence of slashes, and their
// and the length of the text between them. // and the length of the text between them.
// //