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); _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;

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

View File

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

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