Address PR feedback, I hit merge too soon.

This commit is contained in:
Ryan Nowak 2018-08-27 14:52:16 -07:00
parent 3511c8cef0
commit 8d053853bb
4 changed files with 102 additions and 36 deletions

View File

@ -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;

View File

@ -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()

View File

@ -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)

View File

@ -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