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);
|
||||
_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);
|
||||
|
||||
_strings = new string[]
|
||||
|
|
@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var segments = _segments;
|
||||
|
||||
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]);
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var segments = _segments;
|
||||
|
||||
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]);
|
||||
}
|
||||
|
|
@ -165,17 +165,16 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
_text = text;
|
||||
_destination = destination;
|
||||
|
||||
int length = text.Length;
|
||||
ReadOnlySpan<char> span = text.ToLowerInvariant().AsSpan();
|
||||
ref byte p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
||||
var length = text.Length;
|
||||
var span = text.ToLowerInvariant().AsSpan();
|
||||
ref var p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
||||
|
||||
_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);
|
||||
p = Unsafe.Add(ref p, 64);
|
||||
}
|
||||
|
||||
switch (length % 4)
|
||||
{
|
||||
case 1:
|
||||
|
|
@ -224,11 +223,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
public override int GetDestination(string path, PathSegment segment)
|
||||
{
|
||||
int length = segment.Length;
|
||||
ReadOnlySpan<char> span = path.AsSpan(segment.Start, length);
|
||||
ref byte p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
||||
var length = segment.Length;
|
||||
var span = path.AsSpan(segment.Start, length);
|
||||
ref var p = ref Unsafe.As<char, byte>(ref MemoryMarshal.GetReference(span));
|
||||
|
||||
int i = 0;
|
||||
var i = 0;
|
||||
while (length > 3)
|
||||
{
|
||||
var value = Unsafe.ReadUnaligned<ulong>(ref p);
|
||||
|
|
@ -238,10 +237,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
return _defaultDestination;
|
||||
}
|
||||
|
||||
ulong ulongLowerIndicator = value + (0x0080008000800080UL - 0x0041004100410041UL);
|
||||
ulong ulongUpperIndicator = value + (0x0080008000800080UL - 0x005B005B005B005BUL);
|
||||
ulong ulongCombinedIndicator = (ulongLowerIndicator ^ ulongUpperIndicator) & 0x0080008000800080UL;
|
||||
ulong mask = (ulongCombinedIndicator) >> 2;
|
||||
var ulongLowerIndicator = value + (0x0080008000800080UL - 0x0041004100410041UL);
|
||||
var ulongUpperIndicator = value + (0x0080008000800080UL - 0x005B005B005B005BUL);
|
||||
var ulongCombinedIndicator = (ulongLowerIndicator ^ ulongUpperIndicator) & 0x0080008000800080UL;
|
||||
var mask = (ulongCombinedIndicator) >> 2;
|
||||
|
||||
value ^= mask;
|
||||
|
||||
|
|
@ -259,7 +258,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
case 1:
|
||||
{
|
||||
char c = Unsafe.ReadUnaligned<char>(ref p);
|
||||
var c = Unsafe.ReadUnaligned<char>(ref p);
|
||||
if (c != _residue0Lower && c != _residue0Upper)
|
||||
{
|
||||
return _defaultDestination;
|
||||
|
|
@ -270,7 +269,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
case 2:
|
||||
{
|
||||
char c = Unsafe.ReadUnaligned<char>(ref p);
|
||||
var c = Unsafe.ReadUnaligned<char>(ref p);
|
||||
if (c != _residue0Lower && c != _residue0Upper)
|
||||
{
|
||||
return _defaultDestination;
|
||||
|
|
@ -288,7 +287,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
case 3:
|
||||
{
|
||||
char c = Unsafe.ReadUnaligned<char>(ref p);
|
||||
var c = Unsafe.ReadUnaligned<char>(ref p);
|
||||
if (c != _residue0Lower && c != _residue0Upper)
|
||||
{
|
||||
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
|
||||
// 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(
|
||||
int defaultDestination,
|
||||
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>));
|
||||
}
|
||||
|
||||
// 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(
|
||||
ILGenerator il,
|
||||
int defaultDestination,
|
||||
|
|
@ -45,16 +63,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
(string text, int destination)[] entries,
|
||||
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 &= entries.Any(e => e.text.Length >= 4);
|
||||
vectorize = vectorize ?? ShouldVectorize(entries);
|
||||
|
||||
// See comments on Locals for details
|
||||
var locals = new Locals(il, vectorize.Value);
|
||||
|
|
@ -428,32 +438,32 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
/// <summary>
|
||||
/// Holds current character when processing a character at a time.
|
||||
/// </summary>
|
||||
public LocalBuilder UInt16Value { get; set; }
|
||||
public LocalBuilder UInt16Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds current character when processing 4 characters at a time.
|
||||
/// </summary>
|
||||
public LocalBuilder UInt64Value { get; set; }
|
||||
public LocalBuilder UInt64Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to covert casing. See comments where it's used.
|
||||
/// </summary>
|
||||
public LocalBuilder UInt64LowerIndicator { get; set; }
|
||||
|
||||
public LocalBuilder UInt64LowerIndicator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to covert casing. See comments where it's used.
|
||||
/// </summary>
|
||||
public LocalBuilder UInt64UpperIndicator { get; set; }
|
||||
public LocalBuilder UInt64UpperIndicator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds a 'ref byte' reference to the current character (in bytes).
|
||||
/// </summary>
|
||||
public LocalBuilder P { get; set; }
|
||||
public LocalBuilder P { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the relevant portion of the path as a Span[byte].
|
||||
/// </summary>
|
||||
public LocalBuilder Span { get; set; }
|
||||
public LocalBuilder Span { get; }
|
||||
}
|
||||
|
||||
private class Labels
|
||||
|
|
@ -471,6 +481,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
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();
|
||||
|
||||
private Methods()
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
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)
|
||||
{
|
||||
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