Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 8c142fd

Browse files
committed
Merge pull request #2825 from bbowyersmyth/StringStartsWithPt2
String.StartsWith performance - OrdinalCompareSubstring
2 parents 65e111c + 63ff682 commit 8c142fd

File tree

1 file changed

+71
-10
lines changed

1 file changed

+71
-10
lines changed

src/mscorlib/src/System/String.cs

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -372,19 +372,23 @@ private unsafe static bool EqualsHelper(String strA, String strB)
372372
char* a = ap;
373373
char* b = bp;
374374

375-
// unroll the loop
376-
#if AMD64
375+
#if WIN64
376+
// Single int read aligns pointers for the following long reads
377+
// PERF: No length check needed as there is always an int32 worth of string allocated
378+
// This read can also include the null terminator which both strings will have
379+
if (*(int*)a != *(int*)b) return false;
380+
length -= 2; a += 2; b += 2;
381+
377382
// for AMD64 bit platform we unroll by 12 and
378383
// check 3 qword at a time. This is less code
379384
// than the 32 bit case and is a shorter path length.
380-
// Reads are unaligned
381385

382386
while (length >= 12)
383387
{
384388
if (*(long*)a != *(long*)b) return false;
385389
if (*(long*)(a+4) != *(long*)(b+4)) return false;
386390
if (*(long*)(a+8) != *(long*)(b+8)) return false;
387-
a += 12; b += 12; length -= 12;
391+
length -= 12; a += 12; b += 12;
388392
}
389393
#else
390394
while (length >= 10)
@@ -394,7 +398,7 @@ private unsafe static bool EqualsHelper(String strA, String strB)
394398
if (*(int*)(a+4) != *(int*)(b+4)) return false;
395399
if (*(int*)(a+6) != *(int*)(b+6)) return false;
396400
if (*(int*)(a+8) != *(int*)(b+8)) return false;
397-
a += 10; b += 10; length -= 10;
401+
length -= 10; a += 10; b += 10;
398402
}
399403
#endif
400404

@@ -405,13 +409,70 @@ private unsafe static bool EqualsHelper(String strA, String strB)
405409
while (length > 0)
406410
{
407411
if (*(int*)a != *(int*)b) break;
408-
a += 2; b += 2; length -= 2;
412+
length -= 2; a += 2; b += 2;
409413
}
410414

411415
return (length <= 0);
412416
}
413417
}
414-
418+
419+
[System.Security.SecuritySafeCritical] // auto-generated
420+
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
421+
private unsafe static bool StartsWithOrdinalHelper(String str, String startsWith)
422+
{
423+
Contract.Requires(str != null);
424+
Contract.Requires(startsWith != null);
425+
Contract.Requires(str.Length >= startsWith.Length);
426+
427+
int length = startsWith.Length;
428+
429+
fixed (char* ap = &str.m_firstChar) fixed (char* bp = &startsWith.m_firstChar)
430+
{
431+
char* a = ap;
432+
char* b = bp;
433+
434+
#if WIN64
435+
// Single int read aligns pointers for the following long reads
436+
// No length check needed as this method is called when length >= 2
437+
Contract.Assert(length >= 2);
438+
if (*(int*)a != *(int*)b) goto ReturnFalse;
439+
length -= 2; a += 2; b += 2;
440+
441+
while (length >= 12)
442+
{
443+
if (*(long*)a != *(long*)b) goto ReturnFalse;
444+
if (*(long*)(a + 4) != *(long*)(b + 4)) goto ReturnFalse;
445+
if (*(long*)(a + 8) != *(long*)(b + 8)) goto ReturnFalse;
446+
length -= 12; a += 12; b += 12;
447+
}
448+
#else
449+
while (length >= 10)
450+
{
451+
if (*(int*)a != *(int*)b) goto ReturnFalse;
452+
if (*(int*)(a+2) != *(int*)(b+2)) goto ReturnFalse;
453+
if (*(int*)(a+4) != *(int*)(b+4)) goto ReturnFalse;
454+
if (*(int*)(a+6) != *(int*)(b+6)) goto ReturnFalse;
455+
if (*(int*)(a+8) != *(int*)(b+8)) goto ReturnFalse;
456+
length -= 10; a += 10; b += 10;
457+
}
458+
#endif
459+
460+
while (length >= 2)
461+
{
462+
if (*(int*)a != *(int*)b) goto ReturnFalse;
463+
length -= 2; a += 2; b += 2;
464+
}
465+
466+
// PERF: This depends on the fact that the String objects are always zero terminated
467+
// and that the terminating zero is not included in the length. For even string sizes
468+
// this compare can include the zero terminator. Bitwise OR avoids a branch.
469+
return length == 0 | *a == *b;
470+
471+
ReturnFalse:
472+
return false;
473+
}
474+
}
475+
415476
[System.Security.SecuritySafeCritical] // auto-generated
416477
private unsafe static int CompareOrdinalHelper(String strA, String strB)
417478
{
@@ -453,9 +514,9 @@ private unsafe static int CompareOrdinalHelper(String strA, String strB)
453514
diffOffset = 8;
454515
break;
455516
}
517+
length -= 10;
456518
a += 10;
457519
b += 10;
458-
length -= 10;
459520
}
460521

461522
if( diffOffset != -1) {
@@ -479,9 +540,9 @@ private unsafe static int CompareOrdinalHelper(String strA, String strB)
479540
if (*(int*)a != *(int*)b) {
480541
break;
481542
}
543+
length -= 2;
482544
a += 2;
483545
b += 2;
484-
length -= 2;
485546
}
486547

487548
if( length > 0) {
@@ -2559,7 +2620,7 @@ public Boolean StartsWith(String value, StringComparison comparisonType) {
25592620
}
25602621
return (value.Length == 1) ?
25612622
true : // First char is the same and thats all there is to compare
2562-
(nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0);
2623+
StartsWithOrdinalHelper(this, value);
25632624

25642625
case StringComparison.OrdinalIgnoreCase:
25652626
if( this.Length < value.Length) {

0 commit comments

Comments
 (0)