Speed up PathStringHelper (#9709)
This commit is contained in:
parent
5c7c621895
commit
caf4374f83
|
|
@ -1,47 +1,67 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Http.Internal
|
namespace Microsoft.AspNetCore.Http.Internal
|
||||||
{
|
{
|
||||||
internal class PathStringHelper
|
internal static class PathStringHelper
|
||||||
{
|
{
|
||||||
private static bool[] ValidPathChars = {
|
// uint[] bits uses 1 cache line (Array info + 16 bytes)
|
||||||
false, false, false, false, false, false, false, false, // 0x00 - 0x07
|
// bool[] would use 3 cache lines (Array info + 128 bytes)
|
||||||
false, false, false, false, false, false, false, false, // 0x08 - 0x0F
|
// So we use 128 bits rather than 128 bytes/bools
|
||||||
false, false, false, false, false, false, false, false, // 0x10 - 0x17
|
private static readonly uint[] ValidPathChars = {
|
||||||
false, false, false, false, false, false, false, false, // 0x18 - 0x1F
|
0b_0000_0000__0000_0000__0000_0000__0000_0000, // 0x00 - 0x1F
|
||||||
false, true, false, false, true, false, true, true, // 0x20 - 0x27
|
0b_0010_1111__1111_1111__1111_1111__1101_0010, // 0x20 - 0x3F
|
||||||
true, true, true, true, true, true, true, true, // 0x28 - 0x2F
|
0b_1000_0111__1111_1111__1111_1111__1111_1111, // 0x40 - 0x5F
|
||||||
true, true, true, true, true, true, true, true, // 0x30 - 0x37
|
0b_0100_0111__1111_1111__1111_1111__1111_1110, // 0x60 - 0x7F
|
||||||
true, true, true, true, false, true, false, false, // 0x38 - 0x3F
|
|
||||||
true, true, true, true, true, true, true, true, // 0x40 - 0x47
|
|
||||||
true, true, true, true, true, true, true, true, // 0x48 - 0x4F
|
|
||||||
true, true, true, true, true, true, true, true, // 0x50 - 0x57
|
|
||||||
true, true, true, false, false, false, false, true, // 0x58 - 0x5F
|
|
||||||
false, true, true, true, true, true, true, true, // 0x60 - 0x67
|
|
||||||
true, true, true, true, true, true, true, true, // 0x68 - 0x6F
|
|
||||||
true, true, true, true, true, true, true, true, // 0x70 - 0x77
|
|
||||||
true, true, true, false, false, false, true, false, // 0x78 - 0x7F
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsValidPathChar(char c)
|
public static bool IsValidPathChar(char c)
|
||||||
{
|
{
|
||||||
return c < ValidPathChars.Length && ValidPathChars[c];
|
// Use local array and uint .Length compare to elide the bounds check on array access
|
||||||
|
var validChars = ValidPathChars;
|
||||||
|
var i = (int)c;
|
||||||
|
|
||||||
|
// Array is in chunks of 32 bits, so get offset by dividing by 32
|
||||||
|
var offset = i >> 5; // i / 32;
|
||||||
|
// Significant bit position is the remainder of the above calc; i % 32 => i & 31
|
||||||
|
var significantBit = 1u << (i & 31);
|
||||||
|
|
||||||
|
// Check offset in bounds and check if significant bit set
|
||||||
|
return (uint)offset < (uint)validChars.Length &&
|
||||||
|
((validChars[offset] & significantBit) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool IsPercentEncodedChar(string str, int index)
|
public static bool IsPercentEncodedChar(string str, int index)
|
||||||
{
|
{
|
||||||
return index < str.Length - 2
|
var len = (uint)str.Length;
|
||||||
&& str[index] == '%'
|
if (str[index] == '%' && index < len - 2)
|
||||||
&& IsHexadecimalChar(str[index + 1])
|
{
|
||||||
&& IsHexadecimalChar(str[index + 2]);
|
return AreFollowingTwoCharsHex(str, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsHexadecimalChar(char c)
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static bool AreFollowingTwoCharsHex(string str, int index)
|
||||||
{
|
{
|
||||||
return ('0' <= c && c <= '9')
|
Debug.Assert(index < str.Length - 2);
|
||||||
|| ('A' <= c && c <= 'F')
|
|
||||||
|| ('a' <= c && c <= 'f');
|
var c1 = str[index + 1];
|
||||||
|
var c2 = str[index + 2];
|
||||||
|
return IsHexadecimalChar(c1) && IsHexadecimalChar(c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsHexadecimalChar(char c)
|
||||||
|
{
|
||||||
|
// Between 0 - 9 or uppercased between A - F
|
||||||
|
return (uint)(c - '0') <= 9 || (uint)((c & ~0x20) - 'A') <= ('F' - 'A');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ namespace Microsoft.AspNetCore.Http
|
||||||
[TypeConverter(typeof(PathStringConverter))]
|
[TypeConverter(typeof(PathStringConverter))]
|
||||||
public readonly struct PathString : IEquatable<PathString>
|
public readonly struct PathString : IEquatable<PathString>
|
||||||
{
|
{
|
||||||
private static readonly char[] splitChar = { '/' };
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the empty path. This field is read-only.
|
/// Represents the empty path. This field is read-only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -75,27 +73,46 @@ namespace Microsoft.AspNetCore.Http
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var value = _value;
|
||||||
|
var i = 0;
|
||||||
|
for (; i < value.Length; i++)
|
||||||
|
{
|
||||||
|
if (!PathStringHelper.IsValidPathChar(value[i]) || PathStringHelper.IsPercentEncodedChar(value, i))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < value.Length)
|
||||||
|
{
|
||||||
|
return ToEscapedUriComponent(value, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToEscapedUriComponent(string value, int i)
|
||||||
|
{
|
||||||
StringBuilder buffer = null;
|
StringBuilder buffer = null;
|
||||||
|
|
||||||
var start = 0;
|
var start = 0;
|
||||||
var count = 0;
|
var count = i;
|
||||||
var requiresEscaping = false;
|
var requiresEscaping = false;
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
while (i < _value.Length)
|
while (i < value.Length)
|
||||||
{
|
{
|
||||||
var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(_value, i);
|
var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(value, i);
|
||||||
if (PathStringHelper.IsValidPathChar(_value[i]) || isPercentEncodedChar)
|
if (PathStringHelper.IsValidPathChar(value[i]) || isPercentEncodedChar)
|
||||||
{
|
{
|
||||||
if (requiresEscaping)
|
if (requiresEscaping)
|
||||||
{
|
{
|
||||||
// the current segment requires escape
|
// the current segment requires escape
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
buffer = new StringBuilder(_value.Length * 3);
|
buffer = new StringBuilder(value.Length * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Append(Uri.EscapeDataString(_value.Substring(start, count)));
|
buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
|
||||||
|
|
||||||
requiresEscaping = false;
|
requiresEscaping = false;
|
||||||
start = i;
|
start = i;
|
||||||
|
|
@ -120,10 +137,10 @@ namespace Microsoft.AspNetCore.Http
|
||||||
// the current segment doesn't require escape
|
// the current segment doesn't require escape
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
buffer = new StringBuilder(_value.Length * 3);
|
buffer = new StringBuilder(value.Length * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Append(_value, start, count);
|
buffer.Append(value, start, count);
|
||||||
|
|
||||||
requiresEscaping = true;
|
requiresEscaping = true;
|
||||||
start = i;
|
start = i;
|
||||||
|
|
@ -135,9 +152,9 @@ namespace Microsoft.AspNetCore.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == _value.Length && !requiresEscaping)
|
if (count == value.Length && !requiresEscaping)
|
||||||
{
|
{
|
||||||
return _value;
|
return value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -145,16 +162,16 @@ namespace Microsoft.AspNetCore.Http
|
||||||
{
|
{
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
buffer = new StringBuilder(_value.Length * 3);
|
buffer = new StringBuilder(value.Length * 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requiresEscaping)
|
if (requiresEscaping)
|
||||||
{
|
{
|
||||||
buffer.Append(Uri.EscapeDataString(_value.Substring(start, count)));
|
buffer.Append(Uri.EscapeDataString(value.Substring(start, count)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
buffer.Append(_value, start, count);
|
buffer.Append(value, start, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +179,6 @@ namespace Microsoft.AspNetCore.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any
|
/// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any
|
||||||
/// value that is not a path.
|
/// value that is not a path.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue