Address PR feedback, I hit merge too soon.
This commit is contained in:
parent
3511c8cef0
commit
8d053853bb
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
_implementation = new SingleEntryJumpTable(0, -1, "hello-world", 1);
|
_implementation = new SingleEntryJumpTable(0, -1, "hello-world", 1);
|
||||||
_prototype = new SingleEntryAsciiVectorizedJumpTable(0, -2, "hello-world", 1);
|
_prototype = new SingleEntryAsciiVectorizedJumpTable(0, -2, "hello-world", 1);
|
||||||
_trie = new ILEmitTrieJumpTable(0, -1, new [] { ("hello-world", 1), }, vectorize: false, _implementation);
|
_trie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: false, _implementation);
|
||||||
_vectorTrie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: true, _implementation);
|
_vectorTrie = new ILEmitTrieJumpTable(0, -1, new[] { ("hello-world", 1), }, vectorize: true, _implementation);
|
||||||
|
|
||||||
_strings = new string[]
|
_strings = new string[]
|
||||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
var segments = _segments;
|
var segments = _segments;
|
||||||
|
|
||||||
var destination = 0;
|
var destination = 0;
|
||||||
for (int i = 0; i < strings.Length; i++)
|
for (var i = 0; i < strings.Length; i++)
|
||||||
{
|
{
|
||||||
destination = _trie.GetDestination(strings[i], segments[i]);
|
destination = _trie.GetDestination(strings[i], segments[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
var segments = _segments;
|
var segments = _segments;
|
||||||
|
|
||||||
var destination = 0;
|
var destination = 0;
|
||||||
for (int i = 0; i < strings.Length; i++)
|
for (var i = 0; i < strings.Length; i++)
|
||||||
{
|
{
|
||||||
destination = _vectorTrie.GetDestination(strings[i], segments[i]);
|
destination = _vectorTrie.GetDestination(strings[i], segments[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -165,17 +165,16 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
_text = text;
|
_text = text;
|
||||||
_destination = destination;
|
_destination = destination;
|
||||||
|
|
||||||
int length = text.Length;
|
var length = text.Length;
|
||||||
ReadOnlySpan<char> span = text.ToLowerInvariant().AsSpan();
|
var span = text.ToLowerInvariant().AsSpan();
|
||||||
ref byte p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
ref var p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
||||||
|
|
||||||
_values = new ulong[length / 4];
|
_values = new ulong[length / 4];
|
||||||
for (int i = 0; i < length / 4; i++)
|
for (var i = 0; i < length / 4; i++)
|
||||||
{
|
{
|
||||||
_values[i] = Unsafe.ReadUnaligned<ulong>(ref p);
|
_values[i] = Unsafe.ReadUnaligned<ulong>(ref p);
|
||||||
p = Unsafe.Add(ref p, 64);
|
p = Unsafe.Add(ref p, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (length % 4)
|
switch (length % 4)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
|
|
@ -224,11 +223,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
public override int GetDestination(string path, PathSegment segment)
|
public override int GetDestination(string path, PathSegment segment)
|
||||||
{
|
{
|
||||||
int length = segment.Length;
|
var length = segment.Length;
|
||||||
ReadOnlySpan<char> span = path.AsSpan(segment.Start, length);
|
var span = path.AsSpan(segment.Start, length);
|
||||||
ref byte p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
ref var p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
||||||
|
|
||||||
int i = 0;
|
var i = 0;
|
||||||
while (length > 3)
|
while (length > 3)
|
||||||
{
|
{
|
||||||
var value = Unsafe.ReadUnaligned<ulong>(ref p);
|
var value = Unsafe.ReadUnaligned<ulong>(ref p);
|
||||||
|
|
@ -238,10 +237,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return _defaultDestination;
|
return _defaultDestination;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong ulongLowerIndicator = value + (0x0080008000800080UL - 0x0041004100410041UL);
|
var ulongLowerIndicator = value + (0x0080008000800080UL - 0x0041004100410041UL);
|
||||||
ulong ulongUpperIndicator = value + (0x0080008000800080UL - 0x005B005B005B005BUL);
|
var ulongUpperIndicator = value + (0x0080008000800080UL - 0x005B005B005B005BUL);
|
||||||
ulong ulongCombinedIndicator = (ulongLowerIndicator ^ ulongUpperIndicator) & 0x0080008000800080UL;
|
var ulongCombinedIndicator = (ulongLowerIndicator ^ ulongUpperIndicator) & 0x0080008000800080UL;
|
||||||
ulong mask = (ulongCombinedIndicator) >> 2;
|
var mask = (ulongCombinedIndicator) >> 2;
|
||||||
|
|
||||||
value ^= mask;
|
value ^= mask;
|
||||||
|
|
||||||
|
|
@ -259,7 +258,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
char c = Unsafe.ReadUnaligned<char>(ref p);
|
var c = Unsafe.ReadUnaligned<char>(ref p);
|
||||||
if (c != _residue0Lower && c != _residue0Upper)
|
if (c != _residue0Lower && c != _residue0Upper)
|
||||||
{
|
{
|
||||||
return _defaultDestination;
|
return _defaultDestination;
|
||||||
|
|
@ -270,7 +269,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
{
|
{
|
||||||
char c = Unsafe.ReadUnaligned<char>(ref p);
|
var c = Unsafe.ReadUnaligned<char>(ref p);
|
||||||
if (c != _residue0Lower && c != _residue0Upper)
|
if (c != _residue0Lower && c != _residue0Upper)
|
||||||
{
|
{
|
||||||
return _defaultDestination;
|
return _defaultDestination;
|
||||||
|
|
@ -288,7 +287,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
{
|
{
|
||||||
char c = Unsafe.ReadUnaligned<char>(ref p);
|
var c = Unsafe.ReadUnaligned<char>(ref p);
|
||||||
if (c != _residue0Lower && c != _residue0Upper)
|
if (c != _residue0Lower && c != _residue0Upper)
|
||||||
{
|
{
|
||||||
return _defaultDestination;
|
return _defaultDestination;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
// The algorthm we use only works for ASCII text. If we find non-ASCII text in the input
|
// The algorthm we use only works for ASCII text. If we find non-ASCII text in the input
|
||||||
// we need to reject it and let is be processed with a fallback technique.
|
// we need to reject it and let is be processed with a fallback technique.
|
||||||
public const int NotAscii = Int32.MinValue;
|
public const int NotAscii = int.MinValue;
|
||||||
|
|
||||||
|
// Creates a Func of (string path, int start, int length) => destination
|
||||||
|
// Not using PathSegment here because we don't want to mess with visibility checks and
|
||||||
|
// generating IL without it is easier.
|
||||||
public static Func<string, int, int, int> Create(
|
public static Func<string, int, int, int> Create(
|
||||||
int defaultDestination,
|
int defaultDestination,
|
||||||
int exitDestination,
|
int exitDestination,
|
||||||
|
|
@ -38,6 +41,21 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return (Func<string, int, int, int>)method.CreateDelegate(typeof(Func<string, int, int, int>));
|
return (Func<string, int, int, int>)method.CreateDelegate(typeof(Func<string, int, int, int>));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal for testing
|
||||||
|
internal static bool ShouldVectorize((string text, int destination)[] entries)
|
||||||
|
{
|
||||||
|
// There's no value in vectorizing the computation if we're on 32bit or
|
||||||
|
// if no string is long enough. We do the vectorized comparison with uint64 ulongs
|
||||||
|
// which isn't beneficial if they don't map to the native size of the CPU. The
|
||||||
|
// vectorized algorithm introduces additional overhead for casing.
|
||||||
|
|
||||||
|
// Vectorize by default on 64bit (allow override for testing)
|
||||||
|
return (IntPtr.Size == 8) &&
|
||||||
|
|
||||||
|
// Don't vectorize if all of the strings are small (prevents allocating unused locals)
|
||||||
|
entries.Any(e => e.text.Length >= 4);
|
||||||
|
}
|
||||||
|
|
||||||
private static void GenerateMethodBody(
|
private static void GenerateMethodBody(
|
||||||
ILGenerator il,
|
ILGenerator il,
|
||||||
int defaultDestination,
|
int defaultDestination,
|
||||||
|
|
@ -45,16 +63,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
(string text, int destination)[] entries,
|
(string text, int destination)[] entries,
|
||||||
bool? vectorize)
|
bool? vectorize)
|
||||||
{
|
{
|
||||||
// There's no value in vectorizing the computation if we're on 32bit or
|
|
||||||
// if no string is long enough. We do the vectorized comparison with uint64 ulongs
|
|
||||||
// which isn't beneficial if they don't map to the native size of the CPU. The
|
|
||||||
// vectorized algorithm introduces additional overhead for casing.
|
|
||||||
//
|
|
||||||
// Vectorize by default on 64bit (allow override for testing)
|
|
||||||
vectorize = vectorize ?? (IntPtr.Size == 8);
|
|
||||||
|
|
||||||
// Don't vectorize if all of the strings are small (prevents allocating unused locals)
|
vectorize = vectorize ?? ShouldVectorize(entries);
|
||||||
vectorize &= entries.Any(e => e.text.Length >= 4);
|
|
||||||
|
|
||||||
// See comments on Locals for details
|
// See comments on Locals for details
|
||||||
var locals = new Locals(il, vectorize.Value);
|
var locals = new Locals(il, vectorize.Value);
|
||||||
|
|
@ -428,32 +438,32 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds current character when processing a character at a time.
|
/// Holds current character when processing a character at a time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LocalBuilder UInt16Value { get; set; }
|
public LocalBuilder UInt16Value { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds current character when processing 4 characters at a time.
|
/// Holds current character when processing 4 characters at a time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LocalBuilder UInt64Value { get; set; }
|
public LocalBuilder UInt64Value { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to covert casing. See comments where it's used.
|
/// Used to covert casing. See comments where it's used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LocalBuilder UInt64LowerIndicator { get; set; }
|
public LocalBuilder UInt64LowerIndicator { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to covert casing. See comments where it's used.
|
/// Used to covert casing. See comments where it's used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LocalBuilder UInt64UpperIndicator { get; set; }
|
public LocalBuilder UInt64UpperIndicator { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds a 'ref byte' reference to the current character (in bytes).
|
/// Holds a 'ref byte' reference to the current character (in bytes).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LocalBuilder P { get; set; }
|
public LocalBuilder P { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the relevant portion of the path as a Span[byte].
|
/// Holds the relevant portion of the path as a Span[byte].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LocalBuilder Span { get; set; }
|
public LocalBuilder Span { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Labels
|
private class Labels
|
||||||
|
|
@ -471,6 +481,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
private class Methods
|
private class Methods
|
||||||
{
|
{
|
||||||
|
// Caching because the methods won't change, if we're being called once we're likely to
|
||||||
|
// be called again.
|
||||||
public static readonly Methods Instance = new Methods();
|
public static readonly Methods Instance = new Methods();
|
||||||
|
|
||||||
private Methods()
|
private Methods()
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
return _getDestination(path, segment);
|
return _getDestination(path, segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used when we haven't yet initialized the IL trie. We defer compilation of the IL for startup
|
||||||
|
// performance.
|
||||||
private int FallbackGetDestination(string path, PathSegment segment)
|
private int FallbackGetDestination(string path, PathSegment segment)
|
||||||
{
|
{
|
||||||
if (path.Length == 0)
|
if (path.Length == 0)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#if IL_EMIT
|
||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
{
|
||||||
|
public class ILEmitTrieFactoryTest
|
||||||
|
{
|
||||||
|
// We never vectorize on 32bit, so that's part of the test.
|
||||||
|
[Fact]
|
||||||
|
public void ShouldVectorize_ReturnsTrue_ForLargeEnoughStrings()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var is64Bit = IntPtr.Size == 8;
|
||||||
|
var expected = is64Bit;
|
||||||
|
|
||||||
|
var entries = new[]
|
||||||
|
{
|
||||||
|
("foo", 0),
|
||||||
|
("badr", 0),
|
||||||
|
("", 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = ILEmitTrieFactory.ShouldVectorize(entries);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShouldVectorize_ReturnsFalseForSmallStrings()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var entries = new[]
|
||||||
|
{
|
||||||
|
("foo", 0),
|
||||||
|
("sma", 0),
|
||||||
|
("", 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = ILEmitTrieFactory.ShouldVectorize(entries);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue