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

Conversation

benaadams
Copy link
Member

@benaadams benaadams commented Feb 18, 2018

Allows String.Equals (of all forms) to inline for the null and ref equality parts.

Remove case default inline throw as StringSpanHelpers.CheckStringComparison already deals with out of range

Deduplicate calls to StringSpanHelpers.CheckStringComparison and always use it, rather than in some if branches (so the case default can be removed)

Add GetCaseCompareOfComparisonCulture that converts StringComparison to CompareOptions with a single & when comparison is a culture.

Dedupe the calls using it.

Remove the now other duplicated code (for equals)

Resolves #14320
"Consider removing String.IsAscii() fast paths" as CompareOrdinalIgnoreCase(ReadOnlySpan<char>,ReadOnlySpan<char>) has an ascii fast-path in it

Resolves #10293 "Differences between String.CompareOrdinalIgnoreCaseHelper and CompareInfo.CompareOrdinalIgnoreCase" by removing the string version in consolidation

@benaadams
Copy link
Member Author

Total bytes of diff: -1735 (-0.05% of base)
    diff is an improvement.

Total byte diff includes 322 bytes from reconciling methods
        Base had    1 unique methods,      120 unique bytes
        Diff had    4 unique methods,      442 unique bytes

Top file improvements by size (bytes):
       -1735 : System.Private.CoreLib.dasm (-0.05% of base)

1 total files with size differences (1 improved, 0 regressed), 0 unchanged.

Top method regessions by size (bytes):
         335 : System.Private.CoreLib.dasm - String:EqualsHelper(ref,ref,int):bool (0/1 methods)
          77 : System.Private.CoreLib.dasm - StringSpanHelpers:GetStringComparisonException_NotSupported():ref (0/1 methods)
          69 : System.Private.CoreLib.dasm - IdnMapping:PunycodeEncode(ref):ref
          46 : System.Private.CoreLib.dasm - CultureData:get_SLOCALIZEDDISPLAYNAME():ref:this
          39 : System.Private.CoreLib.dasm - SR:GetResourceString(ref,ref):ref
          31 : System.Private.CoreLib.dasm - AssemblyName:ReferenceMatchesDefinition(ref,ref):bool
          28 : System.Private.CoreLib.dasm - DateTimeParse:TryParse(struct,ref,int,byref):bool (2 methods)
          27 : System.Private.CoreLib.dasm - CultureData:EnumSystemLocalesProc(long,int,long):int
          25 : System.Private.CoreLib.dasm - TimeZoneInfo:TryCompareTimeZoneInformationToRegistry(byref,ref,byref):bool
          24 : System.Private.CoreLib.dasm - AppContextDefaultValues:TryParseFrameworkName(ref,byref,byref,byref):bool
          24 : System.Private.CoreLib.dasm - EventSource:AttributeTypeNamesMatch(ref,ref):bool
          23 : System.Private.CoreLib.dasm - TimeZoneInfo:FindSystemTimeZoneById(ref):ref
          20 : System.Private.CoreLib.dasm - OrdinalIgnoreCaseComparer:Equals(ref,ref):bool:this
          20 : System.Private.CoreLib.dasm - TimeZoneInfo:ConvertTimeBySystemTimeZoneId(struct,ref,ref):struct
          20 : System.Private.CoreLib.dasm - TimeZoneInfo:Equals(ref):bool:this (2 methods)
          19 : System.Private.CoreLib.dasm - StringSpanHelpers:ThrowStringComparison_NotSupported() (0/1 methods)
          15 : System.Private.CoreLib.dasm - IdnMapping:GetUnicodeInvariant(ref,int,int):ref:this
          14 : System.Private.CoreLib.dasm - ManifestBuilder:AddKeyword(ref,long):this
          11 : System.Private.CoreLib.dasm - StringSpanHelpers:GetCaseCompareOfComparisonCulture(int):int (0/1 methods)
          10 : System.Private.CoreLib.dasm - AppDomain:PrepareDataForSetup(ref,ref,ref,ref):ref
          10 : System.Private.CoreLib.dasm - ManifestBasedResourceGroveler:CaseInsensitiveManifestResourceStreamLookup(ref,ref):ref:this
          10 : System.Private.CoreLib.dasm - ManifestBuilder:AddOpcode(ref,int):this
          10 : System.Private.CoreLib.dasm - ManifestBuilder:AddTask(ref,int):this
          10 : System.Private.CoreLib.dasm - GlobalizationMode:GetInvariantSwitchValue():bool
          10 : System.Private.CoreLib.dasm - IdnMapping:PunycodeDecode(ref):ref
           6 : System.Private.CoreLib.dasm - AssemblyLoadContext:ValidateAssemblyNameWithSimpleName(ref,ref):ref:this
           3 : System.Private.CoreLib.dasm - String:Equals(ref):bool:this (2 methods)

Top method improvements by size (bytes):
        -478 : System.Private.CoreLib.dasm - String:Equals(ref,ref,int):bool
        -473 : System.Private.CoreLib.dasm - String:Equals(ref,int):bool:this
        -266 : System.Private.CoreLib.dasm - String:LastIndexOf(ref,int,int,int):int:this
        -194 : System.Private.CoreLib.dasm - String:Compare(ref,int,ref,int,int,int):int
        -175 : System.Private.CoreLib.dasm - String:EndsWith(ref,int):bool:this
        -166 : System.Private.CoreLib.dasm - String:Compare(ref,ref,int):int
        -162 : System.Private.CoreLib.dasm - String:StartsWith(ref,int):bool:this
        -142 : System.Private.CoreLib.dasm - String:IndexOf(ushort,int):int:this (2 methods)
        -141 : System.Private.CoreLib.dasm - String:IndexOf(ref,int,int,int):int:this
        -123 : System.Private.CoreLib.dasm - String:Replace(ref,ref,int):ref:this
        -120 : System.Private.CoreLib.dasm - String:EqualsIgnoreCaseAsciiHelper(ref,ref):bool (1/0 methods)
         -72 : System.Private.CoreLib.dasm - StringSpanHelpers:CheckStringComparison(int)
         -52 : System.Private.CoreLib.dasm - Span:StartsWith(struct,struct,int):bool
         -52 : System.Private.CoreLib.dasm - Span:EndsWith(struct,struct,int):bool
         -50 : System.Private.CoreLib.dasm - StringComparer:FromComparison(int):ref
          -5 : System.Private.CoreLib.dasm - String:Equals(ref,ref):bool

43 total methods with size differences (16 improved, 27 regressed), 17123 unchanged.

Regressions (other than centralization of String:EqualsHelper(ref,ref,int)) are from inlining String.Equals

return false;

if (this.Length != value.Length)
if (value is null || value.Length != value.Length)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops

@benaadams
Copy link
Member Author

Hadn't noticed string.CompareOrdinalIgnoreCaseHelper was no longer called; have now removed it

Total bytes of diff: -1838 (-0.05% of base)
    diff is an improvement.

Total byte diff includes 179 bytes from reconciling methods
        Base had    2 unique methods,      263 unique bytes
        Diff had    4 unique methods,      442 unique bytes

Top file improvements by size (bytes):
       -1838 : System.Private.CoreLib.dasm (-0.05% of base)

1 total files with size differences (1 improved, 0 regressed), 0 unchanged.

Top method regessions by size (bytes):
         335 : System.Private.CoreLib.dasm - String:EqualsHelper(ref,ref,int):bool (0/1 methods)
          77 : System.Private.CoreLib.dasm - StringSpanHelpers:GetStringComparisonException_NotSupported():ref (0/1 methods)
          69 : System.Private.CoreLib.dasm - IdnMapping:PunycodeEncode(ref):ref
          49 : System.Private.CoreLib.dasm - CompareInfo:Compare(ref,ref,int):int:this
          46 : System.Private.CoreLib.dasm - CultureData:get_SLOCALIZEDDISPLAYNAME():ref:this

Top method improvements by size (bytes):
        -478 : System.Private.CoreLib.dasm - String:Equals(ref,ref,int):bool
        -473 : System.Private.CoreLib.dasm - String:Equals(ref,int):bool:this
        -266 : System.Private.CoreLib.dasm - String:LastIndexOf(ref,int,int,int):int:this
        -194 : System.Private.CoreLib.dasm - String:Compare(ref,int,ref,int,int,int):int
        -175 : System.Private.CoreLib.dasm - String:EndsWith(ref,int):bool:this

45 total methods with size differences (18 improved, 27 regressed), 17120 unchanged.

@stephentoub
Copy link
Member

Perf?

@jkotas jkotas requested a review from ahsonkhan February 18, 2018 08:34
@benaadams
Copy link
Member Author

Needs some tweaks, e.g. implicit operator ReadOnlySpan<char>(string value) isn't inlining

@benaadams
Copy link
Member Author

Looks like changing

static int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)

to in params will be helpful here https://github.com/dotnet/corefx/issues/27238

@benaadams
Copy link
Member Author

String -> ROS #16440

@ahsonkhan
Copy link

@dotnet-bot test Windows_NT arm Cross Checked corefx_baseline Build and Test
@dotnet-bot test Windows_NT x64_arm64_altjit Checked corefx_baseline
@dotnet-bot test Ubuntu x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x86_arm_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x86 Checked corefx_baseline

throw GetStringComparisonException_NotSupported();
}

[MethodImpl(MethodImplOptions.NoInlining)]
Copy link
Member

@danmoseley danmoseley Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't JIT automatically not-inline methods that unconditionally throw?

            if (!m_IsForceInline && m_IsNoReturn && (basicBlockCount == 1))
            {
                SetNever(InlineObservation::CALLEE_DOES_NOT_RETURN);
            }

Copy link
Member Author

@benaadams benaadams Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method doesn't throw; it just constructs and returns the exception to the method that throws.

Issue is if the throw method does too much then basicBlockCount can be greater than 1 then it no longer counts as a non-returning throw method. Moving the exception constructor, the resource lookup and marking that as non-inlining; and then just throwing the return guarantees that the basicBlockCount is always 1 in the throw method.

NoInlining probably isn't needed here; its a trait for older runtimes that don't know about non-returning methods; but its also not harming anything?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method doesn't throw; it just constructs and returns the exception to the method that throws.

am I misreading? I see throw new ArgumentException(...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eugh; should be return rather than throw

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benaadams
Copy link
Member Author

Errors still

System.Runtime.Serialization.Formatters.Tests.BinaryFormatterTests.ValidateAgainstBlob
s(obj: [], blobs: [\"AAEAAAD/////AQAAAAAAAAAEAQAAABxTeXN0ZW0uQ29sbGVjdG\"..., \"AAEAAAD/////AQAAAAAAAAAEAQAAABxTeXN0ZW0uQ29sbGVjdG\"...]) 

@ahsonkhan
Copy link

Errors still

That is a known issue: dotnet/corefx#27212 (comment)

@ahsonkhan
Copy link

@dotnet-bot test Windows_NT arm Cross Checked corefx_baseline Build and Test
@dotnet-bot test Windows_NT x64_arm64_altjit Checked corefx_baseline
@dotnet-bot test Ubuntu x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x86_arm_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x86 Checked corefx_baseline

@ahsonkhan
Copy link

@dotnet-bot test OSX10.12 x64 Checked Innerloop Build and Test

@benaadams
Copy link
Member Author

@dotnet-bot test Windows_NT x64_arm64_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x86_arm_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x86 Checked corefx_baseline

@benaadams
Copy link
Member Author

@ahsonkhan its complaining

The name 'MemoryMarshal' does not exist in the current context
System.Private.CoreLib 
coreclr\src\mscorlib\src\System\String.Comparison.cs

For

internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
{
    // TODO: Add a vectorized code path, similar to SequenceEqual
    // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900

    int minLength = Math.Min(strA.Length, strB.Length);
    ref char first = ref MemoryMarshal.GetReference(strA);
    ref char second = ref MemoryMarshal.GetReference(strB);

And wants a extra using at top?

using System.Runtime.InteropServices;

@ahsonkhan
Copy link

ahsonkhan commented Feb 23, 2018

its complaining

Yes, we still need the InteropService using directive that you removed here: https://github.com/dotnet/coreclr/pull/16434/files#diff-a43bf8854ecf533cd7e5c7f6b221ea0cL10

That is where MemoryMarshal lives.

@benaadams
Copy link
Member Author

lol, sorry :)


case StringComparison.CurrentCultureIgnoreCase:
return ReplaceCore(oldValue, newValue, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
return ReplaceCore(oldValue, newValue, CultureInfo.CurrentCulture, StringSpanHelpers.GetCaseCompareOfComparisonCulture(comparisonType));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: How much value does this change add (reducing the cases in the switch statement this way)?

I have span-based APIs that have similar structure and am wondering how fruitful it would be to do the same thing there.

return (CompareOptions)((int)comparisonType & (int)CompareOptions.IgnoreCase);
}

[StackTraceHidden]
Copy link

@ahsonkhan ahsonkhan Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am seeing this attribute for the first time. I can infer what it does, but can you explain why we should have it here?

Is it mainly to help debugging by avoiding an extra method showing up in the call stack?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't output the method in the stacktrace; so it looks like it was thrown in the original method that calls it

using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Add back new line.

I have been separating using Internal.* and the rest of the using directives with a new line. Most of the places I have seen do this but it is inconsistent.

Although, I don't think it is worth making the change given CI is mostly green already.

return false;

if (this.Length != value.Length)
if (value is null || this.Length != value.Length)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is value is null better than value == null?

The disassembly is identical.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't affect string as its special cased; but in other cases can cause the compiler to do an implicit conversion to the type then use an overloaded == to compare; has caught me out before.

charA = *a;
charB = *b;

Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
Copy link

@ahsonkhan ahsonkhan Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are now replacing calls to this method (CompareOrdinalIgnoreCaseHelper) with calls to CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)

Should this be 0x7F instead of 0x80?

https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/Globalization/CompareInfo.cs#L569

// in InvariantMode we support all range and not only the ascii characters.
char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x80);

Also, this method doesn't do the (*a <= maxChar) && (*b <= maxChar) checks for each character (which the new method does). Wouldn't we see perf regression now (including the overhead of casting string to span)?

Edit: Ignore the point about the loop having extra checks per character (we are replacing the IsAscii check which was doing this anyway).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the check needs to be <= 0x7F and not <= 0x80 to remain consistent with the IsAscii checks.

if (c>=0x80) {

cc @tarekgh, @jkotas

Copy link
Member Author

@benaadams benaadams Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that;s not right; either should be < maxChar or maxChar should be 0x7f; since the alternative is 0xffff guessing should be 0x7f

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");

// Ordinal equals or lowercase equals if the result ends up in the a-z range
if (charA == charB ||
Copy link

@ahsonkhan ahsonkhan Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which way of writing this method is more optimal?

A: What we are removing:

                    // Ordinal equals or lowercase equals if the result ends up in the a-z range 
                    if (charA == charB ||
                       ((charA | 0x20) == (charB | 0x20) &&
                          (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
                    {
                        a++;
                        b++;
                        length--;
                    }
                    else
                    {
                        return false;
                    }

Versus, B:
https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/Globalization/CompareInfo.cs#L576-L593

                    if (charA == charB)
                    {
                        a++; b++;
                        length--;
                        continue;
                    }

                    // uppercase both chars - notice that we need just one compare per char
                    if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20;
                    if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20;

                    // Return the (case-insensitive) difference between them.
                    if (charA != charB)
                        return charA - charB;

                    // Next char
                    a++; b++;
                    length--;

It seems like (A) is better (at least smaller disassembly): 148 bytes vs 133 bytes (2 instructions).
https://www.diffchecker.com/0hhlUYlf

        [MethodImpl(MethodImplOptions.NoInlining)]
        public unsafe static int TestingA(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
        {
            int length = Math.Min(strA.Length, strB.Length);
            fixed (char* ap = &MemoryMarshal.GetReference(strA))
            fixed (char* bp = &MemoryMarshal.GetReference(strB))
            {
                char* a = ap;
                char* b = bp;

                while (length != 0)
                {
                    int charA = *a;
                    int charB = *b;

                    // Ordinal equals or lowercase equals if the result ends up in the a-z range 
                    if (charA == charB ||
                       ((charA | 0x20) == (charB | 0x20) &&
                          (uint)((charA | 0x20) - 'a') <= ('z' - 'a')))
                    {
                        a++;
                        b++;
                        length--;
                    }
                    else
                    {
                        return charA - charB;
                    }
                }
                return strA.Length - strB.Length;
            }
        }


        [MethodImpl(MethodImplOptions.NoInlining)]
        public unsafe static int TestingB(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
        {
            int length = Math.Min(strA.Length, strB.Length);
            fixed (char* ap = &MemoryMarshal.GetReference(strA))
            fixed (char* bp = &MemoryMarshal.GetReference(strB))
            {
                char* a = ap;
                char* b = bp;

                while (length != 0)
                {
                    int charA = *a;
                    int charB = *b;

                    if (charA == charB)
                    {
                        a++; b++;
                        length--;
                        continue;
                    }

                    // uppercase both chars - notice that we need just one compare per char
                    if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20;
                    if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20;

                    // Return the (case-insensitive) difference between them.
                    if (charA != charB)
                        return charA - charB;

                    // Next char
                    a++; b++;
                    length--;
                }
                return strA.Length - strB.Length;
            }
        }

Related to: https://github.com/dotnet/coreclr/issues/10293#issue-215273397

CompareInfo.CompareOrdinalIgnoreCase checks to see if the chars are equivalent before normalizing case, whereas String.CompareOrdinalIgnoreCaseHelper doesn't. This suggests that the former is optimizing for the chars being the same case, whereas the latter is optimizing for the case where the chars aren't the same case. It's not clear to me that there's a use-case difference between these helpers that would account for this.

cc @AndyAyersMS, @stephentoub

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A is probably a bit faster; B has more branching and does more work.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benaadams can you please update CompareOrdinalIgnoreCase to look like (A). See my TestingA method above.: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/Globalization/CompareInfo.cs#L576-L593

Thanks!

@ahsonkhan
Copy link

ahsonkhan commented Feb 23, 2018

System.Runtime.Extensions.Tests
https://ci.dot.net/job/dotnet_coreclr/job/master/job/jitstress/job/x64_checked_windows_nt_corefx_baseline_prtest/264/consoleFull#-18123546402d31e50d-1517-49fc-92b3-2ca637122019

System.IO.Tests.PathTests.GetFullPath_Windows_NotSupportedExceptionPaths(path: \"bad::$DATA\") [FAIL]
13:16:49         Assert.Throws() Failure
13:16:51   Finished:    System.Net.WebClient.Tests
13:16:51   
13:16:52         Expected: typeof(System.NotSupportedException)
13:16:52   === TEST EXECUTION SUMMARY ===
13:16:53         Actual:   (No exception was thrown)
13:16:53         Stack Trace:
13:16:53      System.Net.WebClient.Tests  Total: 36, Errors: 0, Failed: 0, Skipped: 0, Time: 41.393s
13:16:55            D:\j\workspace\x86_checked_w---92c0aac3\_\fx\src\System.Runtime.Extensions\tests\System\IO\PathTests.cs(753,0): at System.IO.Tests.PathTests.GetFullPath_Windows_NotSupportedExceptionPaths(String path)
13:16:55   ----- end 13:16:52.41 ----- exit code 0 ----------------------------------------------------------
13:16:57      System.IO.Tests.PathTests.GetFullPath_Windows_NotSupportedExceptionPaths(path: \"C  :\") [FAIL]
13:16:58         Assert.Throws() Failure
13:16:59         Expected: typeof(System.NotSupportedException)
13:17:01         Actual:   (No exception was thrown)
13:17:04         Stack Trace:
13:17:05            D:\j\workspace\x86_checked_w---92c0aac3\_\fx\src\System.Runtime.Extensions\tests\System\IO\PathTests.cs(753,0): at System.IO.Tests.PathTests.GetFullPath_Windows_NotSupportedExceptionPaths(String path)
13:17:05      System.IO.Tests.PathTests.GetFullPath_Windows_NotSupportedExceptionPaths(path: \"C  :\\\\somedir\") [FAIL]
13:17:05         Assert.Throws() Failure
13:17:05         Expected: typeof(System.NotSupportedException)
13:17:05         Actual:   (No exception was thrown)
13:17:05         Stack Trace:
13:17:05            D:\j\workspace\x86_checked_w---92c0aac3\_\fx\src\System.Runtime.Extensions\tests\System\IO\PathTests.cs(753,0): at System.IO.Tests.PathTests.GetFullPath_Windows_NotSupportedExceptionPaths(String path)
13:17:05      System.IO.Tests.PathTests.GetFullPath_Windows_UNC_Valid(expected: \"\\\\\\\\LOCALHOST\\\\share\\\\test.txt.~SS\", input: \"\\\\\\\\LOCALHOST\\\\share\\\\test.txt.~SS\") [FAIL]
13:17:05         Assert.Equal() Failure
13:17:05                                          � (pos 30)
13:17:05         Expected: úúúT\share\test.txt.~SS
13:17:05         Actual:   úúúT\share\test.txt.~SS\0
13:17:05                                          � (pos 30)
13:17:05         Stack Trace:
13:17:05            D:\j\workspace\x86_checked_w---92c0aac3\_\fx\src\System.Runtime.Extensions\tests\System\IO\PathTests.cs(956,0): at System.IO.Tests.PathTests.GetFullPath_Windows_UNC_Valid(String expected, String input)
13:17:05      System.IO.Tests.PathTests.GetFullPath_Windows_UNC_Valid(expected: \"\\\\\\\\.\\\\UNC\\\\LOCALHOST\\\\shareF\\\\test.txt.~SS\", input: \"\\\\\\\\.\\\\UNC\\\\LOCALHOST\\\\shareF\\\\test.txt.~SS\") [FAIL]
13:17:05         Assert.Equal() Failure
13:17:05                                          � (pos 37)
13:17:05         Expected: úúú\shareF\test.txt.~SS
13:17:05         Actual:   úúú\shareF\test.txt.~SS\0

Could this have something to do with a bug in https://github.com/dotnet/coreclr/blob/master/src/mscorlib/shared/System/Span.NonGeneric.cs#L337?

Or is this related to: dotnet/corefx#27348

cc @JeremyKuhne

Edit: Could be related to my change here - cb4dd60#diff-a43bf8854ecf533cd7e5c7f6b221ea0cR658

I am going to dig deeper.

@ahsonkhan
Copy link

}

Debug.Fail("StringComparison outside range");
return null;
Copy link
Member

@tarekgh tarekgh Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return null; [](start = 12, length = 12)

this is wrong and we have to throw #Closed

Copy link
Member Author

@benaadams benaadams Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It throws at the start of the function; second throw here would be redundant and just bloat the method #Closed

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We throw in the beginning. We should never reach this point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I missed this line :-) never mind then


In reply to: 170394604 [](ancestors = 170394604)

}

Debug.Fail("StringComparison outside range");
return 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return 0; [](start = 11, length = 10)

ditto

Copy link

@ahsonkhan ahsonkhan Feb 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. We should never reach here.

Copy link

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than updating CompareOrdinalIgnoreCase, looks good to me.

@ahsonkhan
Copy link

@dotnet-bot test Windows_NT x64_arm64_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x86_arm_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x86 Checked corefx_baseline

@ahsonkhan
Copy link

https://ci.dot.net/job/dotnet_coreclr/job/master/job/jitstress/job/x86_arm_altjit_checked_windows_nt_corefx_baseline_prtest/31/consoleFull#124204067533fe3402-0c0f-45a7-a707-47164965bab5

https://github.com/dotnet/corefx/issues/23435

https://ci.dot.net/job/dotnet_coreclr/job/master/job/jitstress/job/x64_arm64_altjit_checked_windows_nt_corefx_baseline_prtest/31/consoleFull#62222254033fe3402-0c0f-45a7-a707-47164965bab5

19:47:54 Starting: System.ValueTuple.Tests
19:48:00
19:48:00
Assert failure(PID 7272 [0x00001c68], Thread: 7892 [0x1ed4]): Assertion failed 'varDsc->lvOnFrame && !varDsc->lvRegister' in 'System.Tests.ValueTupleTests:LongTuplesWithNull()' (IL size 741)
19:48:00

Is this a known issue?

cc @AndyAyersMS, @dotnet/jit-contrib

@AndyAyersMS
Copy link
Member

Yes, it's a pre-existing issue, see #16377.

@ahsonkhan
Copy link

Yes, it's a pre-existing issue, see #16377.

Lol, I knew it seemed familiar. I searched the repo for "LongTuplesWithNull" but missed it (I didn't wait long enough to see the results under Issues).

@ahsonkhan
Copy link

OK, @benaadams, can you please address the code review feedback, whenever possible, and we can get this merged :)

Also:

Perf?

@benaadams
Copy link
Member Author

Let me get some perf tests

@jkotas could there be an issue for R2R that the initial slim String.Equal won't inline (crossing assembly boundries) so this would introduce two calls?

@jkotas
Copy link
Member

jkotas commented Feb 27, 2018

could there be an issue for R2R that the initial slim String.Equal won't inline

It is like that today already, so this is regressing R2R. Right?

@benaadams
Copy link
Member Author

It is like that today already, so this is regressing R2R. Right?

Not sure; am wondering how to check; would I need to crossgen the performance test?

@ahsonkhan
Copy link

ahsonkhan commented Mar 2, 2018

@benaadams, any plans to get this in this week?

Do you want me to take over and resolve the merge conflicts?

@benaadams
Copy link
Member Author

@ahsonkhan I'd like to hold off on it a bit till I do a bit more perf analysis; as I think it will mean the whole of corefx will have to make 2 calls instead of 1 (as everything is crossgen'd to the runtime store?)

If that's the case, there are still some improvements that could be incorporated from it without splitting to two calls (though I'm getting on a plane shortly)

@ahsonkhan
Copy link

@benaadams, do you plan to update this PR as part of 2.1 or 2.2?

@jkotas jkotas added this to the 2.2.0 milestone Apr 11, 2018
@jkotas
Copy link
Member

jkotas commented Apr 11, 2018

This is not for 2.1.

jkotas added a commit that referenced this pull request May 6, 2018
This change makes the code both smaller and faster. For example, the following is about 1.4x faster with this change:

```
ReadOnlySpan<char> s1 = "Hello world";
ReadOnlySpan<char> s2 = "world";
for (int i = 0; i < 100000000; i++) s1.EndsWith(s2, StringComparison.OrdinalIgnoreCase);
```

Also, I have ported GetCaseCompareOfComparisonCulture code size optimization from #16434 while I was on it because of it fit well with the rest of the changes.
@jkotas
Copy link
Member

jkotas commented May 16, 2018

Most of these changes got submitted via independent smaller PRs. If there is anything good still left, please submit a new PR with what is remaining.

@jkotas jkotas closed this May 16, 2018
@benaadams benaadams deleted the string.equals-inline branch January 11, 2019 21:38
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants