95 lines
4.3 KiB
C#
95 lines
4.3 KiB
C#
// 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;
|
|
|
|
namespace Microsoft.AspNetCore.Routing.Matching
|
|
{
|
|
internal static class JumpTableBuilder
|
|
{
|
|
public static readonly int InvalidDestination = -1;
|
|
|
|
public static JumpTable Build(int defaultDestination, int exitDestination, (string text, int destination)[] pathEntries)
|
|
{
|
|
if (defaultDestination == InvalidDestination)
|
|
{
|
|
var message = $"{nameof(defaultDestination)} is not set. Please report this as a bug.";
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
|
|
if (exitDestination == InvalidDestination)
|
|
{
|
|
var message = $"{nameof(exitDestination)} is not set. Please report this as a bug.";
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
|
|
// The JumpTable implementation is chosen based on the number of entries.
|
|
//
|
|
// Basically the concerns that we're juggling here are that different implementations
|
|
// make sense depending on the characteristics of the entries.
|
|
//
|
|
// On netcoreapp we support IL generation of optimized tries that is much faster
|
|
// than anything we can do with string.Compare or dictionaries. However the IL emit
|
|
// strategy requires us to produce a fallback jump table - see comments on the class.
|
|
|
|
// We have an optimized fast path for zero entries since we don't have to
|
|
// do any string comparisons.
|
|
if (pathEntries == null || pathEntries.Length == 0)
|
|
{
|
|
return new ZeroEntryJumpTable(defaultDestination, exitDestination);
|
|
}
|
|
|
|
// The IL Emit jump table is not faster for a single entry - but we have an optimized version when all text
|
|
// is ASCII
|
|
if (pathEntries.Length == 1 && Ascii.IsAscii(pathEntries[0].text))
|
|
{
|
|
var entry = pathEntries[0];
|
|
return new SingleEntryAsciiJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
|
|
}
|
|
|
|
// We have a fallback that works for non-ASCII
|
|
if (pathEntries.Length == 1)
|
|
{
|
|
var entry = pathEntries[0];
|
|
return new SingleEntryJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
|
|
}
|
|
|
|
// We choose a hard upper bound of 100 as the limit for when we switch to a dictionary
|
|
// over a trie. The reason is that while the dictionary has a bigger constant factor,
|
|
// it is O(1) vs a trie which is O(M * log(N)). Our perf testing shows that the trie
|
|
// is better for ~90 entries based on all of Azure's route table. Anything above 100 edges
|
|
// we'd consider to be a very very large node, and so while we don't think anyone will
|
|
// have a node this large in practice, we want to make sure the performance is reasonable
|
|
// for any size.
|
|
//
|
|
// Additionally if we're on 32bit, the scalability is worse, so switch to the dictionary at 50
|
|
// entries.
|
|
var threshold = IntPtr.Size == 8 ? 100 : 50;
|
|
if (pathEntries.Length >= threshold)
|
|
{
|
|
return new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
|
|
}
|
|
|
|
// If we have more than a single string, the IL emit strategy is the fastest - but we need to decide
|
|
// what do for the fallback case.
|
|
JumpTable fallback;
|
|
|
|
// Based on our testing a linear search is still faster than a dictionary at ten entries.
|
|
if (pathEntries.Length <= 10)
|
|
{
|
|
fallback = new LinearSearchJumpTable(defaultDestination, exitDestination, pathEntries);
|
|
}
|
|
else
|
|
{
|
|
fallback = new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
|
|
}
|
|
|
|
#if IL_EMIT
|
|
return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
|
|
#else
|
|
return fallback;
|
|
#endif
|
|
}
|
|
}
|
|
}
|