Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Faster StartsWithSegments Ordinal check #521

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/Microsoft.AspNet.Http.Abstractions/PathString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,85 @@ public bool StartsWithSegments(PathString other, StringComparison comparisonType
{
var value1 = Value ?? string.Empty;
var value2 = other.Value ?? string.Empty;

if (comparisonType == StringComparison.Ordinal)
{
return StartsWithOrdinal(value1, value2);
}

if (value1.StartsWith(value2, comparisonType))
{
return value1.Length == value2.Length || value1[value2.Length] == '/';
}
return false;
}

private unsafe static bool StartsWithOrdinal(string path, string startsWith)
{
const int longLength = sizeof(long) / sizeof(char);
const int long2Length = (sizeof(long) + sizeof(long)) / sizeof(char);
const int intLength = sizeof(int) / sizeof(char);

var count = startsWith.Length;
if (count > path.Length)
{
return false;
}
else if (count == 0 && (path.Length == 0 || (path.Length > 0 && path[0] == '/')))
{
return true;
}

fixed (char* pPath = path)
fixed (char* pStartsWith = startsWith)
{
var cpPath = pPath;
var cpStartsWith = pStartsWith;
if (count >= longLength)
{
var lpPath = (long*)pPath;
var lpStartsWith = (long*)pStartsWith;
while (count >= long2Length)
{
count -= long2Length;
cpPath += long2Length;
cpStartsWith += long2Length;
if (*lpPath != *lpStartsWith) return false;
if (*(lpPath + 1) != *(lpStartsWith + 1)) return false;
if (count > longLength)
{
lpPath += 2;
lpStartsWith += 2;
}
}
if (count >= longLength)
{
count -= longLength;
cpPath += longLength;
cpStartsWith += longLength;
if (*lpPath != *lpStartsWith) return false;
}
}
if (count >= intLength)
{
count -= intLength;
if (*(int*)cpPath != *(int*)cpStartsWith) return false;
cpPath += intLength;
cpStartsWith += intLength;

}
if (count > 0)
{
if (*cpPath++ != *cpStartsWith++) return false;
}
if (path.Length > startsWith.Length)
{
if (*cpPath != '/') return false;
}
}
return true;
}

/// <summary>
/// Determines whether the beginning of this PathString instance matches the specified <see cref="PathString"/> when compared
/// using the specified comparison option and returns the remaining segments.
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.AspNet.Http.Abstractions/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
},
"compilationOptions": {
"warningsAsErrors": true,
"keyFile": "../../tools/Key.snk"
"keyFile": "../../tools/Key.snk",
"allowUnsafe": true
},
"dependencies": {
"Microsoft.AspNet.Http.Features": "1.0.0-*",
Expand Down
18 changes: 18 additions & 0 deletions test/Microsoft.AspNet.Http.Abstractions.Tests/PathStringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ public void StartsWithSegmentsWithRemainder_DoesACaseInsensitiveMatch(string sou
[InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)]
[InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)]
[InlineData(null, null, StringComparison.Ordinal, true)]
[InlineData("", "", StringComparison.Ordinal, true)]
[InlineData("/", "", StringComparison.Ordinal, true)]
[InlineData("//", "/", StringComparison.Ordinal, true)]
[InlineData("/123", "/123", StringComparison.Ordinal, true)]
[InlineData("/123a", "/123", StringComparison.Ordinal, false)]
[InlineData("/123/", "/123", StringComparison.Ordinal, true)]
[InlineData("/123a", "/123", StringComparison.Ordinal, false)]
[InlineData("/1234/", "/1234", StringComparison.Ordinal, true)]
[InlineData("/1234567812345678123456/", "/1234567812345678123456", StringComparison.Ordinal, true)]
[InlineData("/1234567812345678123456/", "/12a4567812345678123456", StringComparison.Ordinal, false)]
[InlineData("/1234567812345678123456/", "/123456a812345678123456", StringComparison.Ordinal, false)]
[InlineData("/1234567812345678123456/", "/1234567812a45678123456", StringComparison.Ordinal, false)]
[InlineData("/1234567812345678123456/", "/12345678123456a8123456", StringComparison.Ordinal, false)]
[InlineData("/1234567812345678123456/", "/123456781234567812a456", StringComparison.Ordinal, false)]
[InlineData("/1234567812345678123456/", "/12345678123456781234a6", StringComparison.Ordinal, false)]
[InlineData("/1234567812345678123456/", "/123456781234567812345a", StringComparison.Ordinal, false)]
[InlineData("/12345678123456781234567/", "/1234567812345678123456", StringComparison.Ordinal, false)]
public void StartsWithSegments_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
{
var source = new PathString(sourcePath);
Expand Down