From 63ff682561f6a3e3b0e2f148d81865ad89cfa306 Mon Sep 17 00:00:00 2001 From: Bruce Bowyer-Smyth Date: Tue, 26 Jan 2016 15:26:38 +1000 Subject: [PATCH] String.StartsWith performance - StartsWithOrdinalHelper --- src/mscorlib/src/System/String.cs | 81 ++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/src/mscorlib/src/System/String.cs b/src/mscorlib/src/System/String.cs index 2479505..7939fde 100644 --- a/src/mscorlib/src/System/String.cs +++ b/src/mscorlib/src/System/String.cs @@ -372,19 +372,23 @@ namespace System { char* a = ap; char* b = bp; - // unroll the loop -#if AMD64 +#if WIN64 + // Single int read aligns pointers for the following long reads + // PERF: No length check needed as there is always an int32 worth of string allocated + // This read can also include the null terminator which both strings will have + if (*(int*)a != *(int*)b) return false; + length -= 2; a += 2; b += 2; + // for AMD64 bit platform we unroll by 12 and // check 3 qword at a time. This is less code // than the 32 bit case and is a shorter path length. - // Reads are unaligned while (length >= 12) { if (*(long*)a != *(long*)b) return false; if (*(long*)(a+4) != *(long*)(b+4)) return false; if (*(long*)(a+8) != *(long*)(b+8)) return false; - a += 12; b += 12; length -= 12; + length -= 12; a += 12; b += 12; } #else while (length >= 10) @@ -394,7 +398,7 @@ namespace System { if (*(int*)(a+4) != *(int*)(b+4)) return false; if (*(int*)(a+6) != *(int*)(b+6)) return false; if (*(int*)(a+8) != *(int*)(b+8)) return false; - a += 10; b += 10; length -= 10; + length -= 10; a += 10; b += 10; } #endif @@ -405,13 +409,70 @@ namespace System { while (length > 0) { if (*(int*)a != *(int*)b) break; - a += 2; b += 2; length -= 2; + length -= 2; a += 2; b += 2; } return (length <= 0); } } - + + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private unsafe static bool StartsWithOrdinalHelper(String str, String startsWith) + { + Contract.Requires(str != null); + Contract.Requires(startsWith != null); + Contract.Requires(str.Length >= startsWith.Length); + + int length = startsWith.Length; + + fixed (char* ap = &str.m_firstChar) fixed (char* bp = &startsWith.m_firstChar) + { + char* a = ap; + char* b = bp; + +#if WIN64 + // Single int read aligns pointers for the following long reads + // No length check needed as this method is called when length >= 2 + Contract.Assert(length >= 2); + if (*(int*)a != *(int*)b) goto ReturnFalse; + length -= 2; a += 2; b += 2; + + while (length >= 12) + { + if (*(long*)a != *(long*)b) goto ReturnFalse; + if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse; + if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse; + length -= 12; a += 12; b += 12; + } +#else + while (length >= 10) + { + if (*(int*)a != *(int*)b) goto ReturnFalse; + if (*(int*)(a+2) != *(int*)(b+2)) goto ReturnFalse; + if (*(int*)(a+4) != *(int*)(b+4)) goto ReturnFalse; + if (*(int*)(a+6) != *(int*)(b+6)) goto ReturnFalse; + if (*(int*)(a+8) != *(int*)(b+8)) goto ReturnFalse; + length -= 10; a += 10; b += 10; + } +#endif + + while (length >= 2) + { + if (*(int*)a != *(int*)b) goto ReturnFalse; + length -= 2; a += 2; b += 2; + } + + // PERF: This depends on the fact that the String objects are always zero terminated + // and that the terminating zero is not included in the length. For even string sizes + // this compare can include the zero terminator. Bitwise OR avoids a branch. + return length == 0 | *a == *b; + + ReturnFalse: + return false; + } + } + [System.Security.SecuritySafeCritical] // auto-generated private unsafe static int CompareOrdinalHelper(String strA, String strB) { @@ -453,9 +514,9 @@ namespace System { diffOffset = 8; break; } + length -= 10; a += 10; b += 10; - length -= 10; } if( diffOffset != -1) { @@ -479,9 +540,9 @@ namespace System { if (*(int*)a != *(int*)b) { break; } + length -= 2; a += 2; b += 2; - length -= 2; } if( length > 0) { @@ -2559,7 +2620,7 @@ namespace System { } return (value.Length == 1) ? true : // First char is the same and thats all there is to compare - (nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0); + StartsWithOrdinalHelper(this, value); case StringComparison.OrdinalIgnoreCase: if( this.Length < value.Length) { -- 2.7.4