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

Use string.IsNullOrEmpty to eliminate bounds check to first char#17512

Merged
jkotas merged 3 commits into
dotnet:masterfrom
benaadams:IsNullOrEmpty-
Apr 12, 2018
Merged

Use string.IsNullOrEmpty to eliminate bounds check to first char#17512
jkotas merged 3 commits into
dotnet:masterfrom
benaadams:IsNullOrEmpty-

Conversation

@benaadams
Copy link
Copy Markdown
Member

@benaadams benaadams commented Apr 11, 2018

Total bytes of diff: -649 (-0.02% of base)
    diff is an improvement.

Total byte diff includes 0 bytes from reconciling methods
        Base had    0 unique methods,        0 unique bytes
        Diff had    0 unique methods,        0 unique bytes

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

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

Top method improvements by size (bytes):
         -64 : System.Private.CoreLib.dasm - Win32Marshal:GetExceptionForWin32Error(int,ref):ref
         -57 : System.Private.CoreLib.dasm - TimeZoneInfo:GetLocalizedNamesByRegistryKey(ref,byref,byref,byref)
         -32 : System.Private.CoreLib.dasm - String:Concat(ref,ref,ref,ref):ref
         -32 : System.Private.CoreLib.dasm - Path:CombineInternal(ref,ref,ref,ref):ref
         -24 : System.Private.CoreLib.dasm - String:Concat(ref,ref):ref (2 methods)
         -24 : System.Private.CoreLib.dasm - String:Concat(ref,ref,ref):ref (2 methods)
         -24 : System.Private.CoreLib.dasm - Path:CombineInternal(ref,ref,ref):ref
         -24 : System.Private.CoreLib.dasm - CalendarData:.ctor(ref,ushort,bool):this
         -23 : System.Private.CoreLib.dasm - Environment:SetEnvironmentVariableCore(ref,ref)
         -22 : System.Private.CoreLib.dasm - String:MakeSeparatorList(ref,byref,byref):this
         -17 : System.Private.CoreLib.dasm - PathInternal:NormalizeDirectorySeparators(ref):ref
         -16 : System.Private.CoreLib.dasm - Path:CombineInternal(ref,ref):ref
         -16 : System.Private.CoreLib.dasm - PathInternal:EqualStartingCharacterCount(ref,ref,bool):int
         -16 : System.Private.CoreLib.dasm - CalendarData:InitializeEraNames(ref,ushort):this
         -16 : System.Private.CoreLib.dasm - CalendarData:InitializeAbbreviatedEraNames(ref,ushort):this
         -16 : System.Private.CoreLib.dasm - ContractHelper:GetDisplayMessage(int,ref,ref):ref
         -11 : System.Private.CoreLib.dasm - CoreLib:FixupCoreLibName(ref):ref
         -11 : System.Private.CoreLib.dasm - String:Concat(ref):ref (5 methods)
         -11 : System.Private.CoreLib.dasm - CultureData:get_SENGDISPLAYNAME():ref:this
         -11 : System.Private.CoreLib.dasm - CultureData:get_SNATIVEDISPLAYNAME():ref:this
          -8 : System.Private.CoreLib.dasm - AppContextDefaultValues:TryParseFrameworkName(ref,byref,byref,byref):bool
          -8 : System.Private.CoreLib.dasm - Environment:ValidateVariableAndValue(ref,byref)
          -8 : System.Private.CoreLib.dasm - ArgumentException:get_Message():ref:this
          -8 : System.Private.CoreLib.dasm - Contract:AssertMustUseRewriter(int,ref)
          -8 : System.Private.CoreLib.dasm - Contract:ReportFailure(int,ref,ref,ref)
          -8 : System.Private.CoreLib.dasm - EventPipeProviderConfiguration:.ctor(ref,long,int):this
          -8 : System.Private.CoreLib.dasm - EventPipeConfiguration:.ctor(ref,int):this
          -8 : System.Private.CoreLib.dasm - Path:GetRelativePath(ref,ref,int):ref
          -8 : System.Private.CoreLib.dasm - PathInternal:EndsWithPeriodOrSpace(ref):bool
          -8 : System.Private.CoreLib.dasm - CultureInfo:get_Parent():ref:this
          -8 : System.Private.CoreLib.dasm - CultureData:GetCultureData(int,bool):ref
          -8 : System.Private.CoreLib.dasm - CultureData:get_TimeSeparator():ref:this
          -8 : System.Private.CoreLib.dasm - CultureData:GetNFIValues(ref):this
          -8 : System.Private.CoreLib.dasm - NumberFormatInfo:.ctor(ref):this
          -8 : System.Private.CoreLib.dasm - SystemTypeMarshaler:ConvertToManaged(long,byref)
          -8 : System.Private.CoreLib.dasm - StringBuilder:Insert(int,ref,int):ref:this
          -8 : System.Private.CoreLib.dasm - AssemblyLoadContext:ValidateAssemblyNameWithSimpleName(ref,ref):ref:this
          -8 : System.Private.CoreLib.dasm - ExternalException:ToString():ref:this
          -8 : System.Private.CoreLib.dasm - ContractHelper:TriggerFailure(int,ref,ref,ref,ref)
          -8 : System.Private.CoreLib.dasm - TypeForwardedFromAttribute:.ctor(ref):this
          -4 : System.Private.CoreLib.dasm - ResourceManager:GetStringFromPRI(ref,ref,ref):ref:this
          -2 : System.Private.CoreLib.dasm - String:IsNullOrEmpty(ref):bool
          -2 : System.Private.CoreLib.dasm - Environment:SetEnvironmentVariableCore(ref,ref,int)
          -2 : System.Private.CoreLib.dasm - TimeZoneInfo:TryGetLocalizedNameByMuiNativeResource(ref):ref
          -2 : System.Private.CoreLib.dasm - CultureData:GetCultureDataForRegion(ref,bool):ref
          -2 : System.Private.CoreLib.dasm - CultureData:GetCultureData(ref,bool):ref
          -2 : System.Private.CoreLib.dasm - CultureData:get_SLOCALIZEDDISPLAYNAME():ref:this
          -2 : System.Private.CoreLib.dasm - CultureData:get_IsInvariantCulture():bool:this
          -2 : System.Private.CoreLib.dasm - Assembly:LoadFromResolveHandler(ref,ref):ref
          -2 : System.Private.CoreLib.dasm - WindowsRuntimeMetadata:OnDesignerNamespaceResolveEvent(ref,ref):ref

50 total methods with size differences (50 improved, 0 regressed), 17247 unchanged.

From aspnet/KestrelHttpServer#2347 (comment)

public static bool StartsWithBracket(string hostText)
{
    if (hostText == null || 0u >= (uint)hostText.Length)
    {
        return false;
    }

    var firstChar = hostText[0];
    if (firstChar == '[')
    {
        return true;
    }
    
    return false;
}

; Desktop CLR v4.7.2563.00 (clr.dll) on amd64.

Program.StartsWithBracket(System.String)
    L0000: test rcx, rcx
    L0003: jz L000c
    L0005: mov eax, [rcx+0x8]
    L0008: test eax, eax
    L000a: jnz L000f
    L000c: xor eax, eax
    L000e: ret
    L000f: cmp word [rcx+0xc], 0x5b
    L0014: jnz L001c
    L0016: mov eax, 0x1
    L001b: ret
    L001c: xor eax, eax
    L001e: ret

vs

public static bool StartsWithBracket(string hostText)
{
    if (hostText == null || hostText.Length == 0)
    {
        return false;
    }

    var firstChar = hostText[0];
    if (firstChar == '[')
    {
        return true;
    }
    
    return false;
}

; Desktop CLR v4.7.2563.00 (clr.dll) on amd64.

Program.StartsWithBracket(System.String)
    L0000: sub rsp, 0x28
    L0004: test rcx, rcx
    L0007: jz L0010
    L0009: mov edx, [rcx+0x8]
    L000c: test edx, edx
    L000e: jnz L0017
    L0010: xor eax, eax
    L0012: add rsp, 0x28
    L0016: ret
    L0017: cmp edx, 0x0
    L001a: jbe L0034
    L001c: cmp word [rcx+0xc], 0x5b
    L0021: jnz L002d
    L0023: mov eax, 0x1
    L0028: add rsp, 0x28
    L002c: ret
    L002d: xor eax, eax
    L002f: add rsp, 0x28
    L0033: ret
    L0034: call 0x7ffaff4223c0
    L0039: int3

@stephentoub
Copy link
Copy Markdown
Member

cc: @AndyAyersMS

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

Sheesh, a never ending stream of hacks because the JIT can't figure things out. I need to figure out a way to clone myself and fix it...

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

public static bool StartsWithBracket(string hostText)

I suggest you post code that actually uses IsNullOrEmpty, you may have a surprise...

@benaadams
Copy link
Copy Markdown
Member Author

benaadams commented Apr 11, 2018

I suggest you post code that actually uses IsNullOrEmpty, you may have a surprise...

Yeah that's worse than either example

public static bool StartsWithBracket(string hostText)
{
    if (string.IsNullOrEmpty(hostText))
    {
        return false;
    }

    var firstChar = hostText[0];
    if (firstChar == '[')
    {
        return true;
    }
    
    return false;
}


; Desktop CLR v4.7.2563.00 (clr.dll) on amd64.

Program.StartsWithBracket(System.String)
    L0000: sub rsp, 0x28
    L0004: test rcx, rcx
    L0007: jz L0017
    L0009: cmp dword [rcx+0x8], 0x0
    L000d: setz al
    L0010: movzx eax, al
    L0013: test eax, eax
    L0015: jz L001e
    L0017: xor eax, eax
    L0019: add rsp, 0x28
    L001d: ret
    L001e: cmp dword [rcx+0x8], 0x0
    L0022: jbe L003c
    L0024: cmp word [rcx+0xc], 0x5b
    L0029: jnz L0035
    L002b: mov eax, 0x1
    L0030: add rsp, 0x28
    L0034: ret
    L0035: xor eax, eax
    L0037: add rsp, 0x28
    L003b: ret
    L003c: call 0x7ffaff4223c0
    L0041: int3

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

Try return (value == null || 0u >= (uint)value.Length) ? true : false; instead. The pattern currently used by IsNullOrEmpty has a CQ issue that I reported 3 years ago and of course, it's still not fixed...

@lahma
Copy link
Copy Markdown

lahma commented Apr 11, 2018

Could the same kind of precondition check also help with IsNullOrWhiteSpace? I for example tend to call it just to be on safe side when input usually is string.Empty.

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

Could the same kind of precondition check also help with IsNullOrWhiteSpace? I for example tend to call it just to be on safe side when input usually is string.Empty.

That one contains a trivial for loop where the JIT should have no problem eliminating the range check. Besides, it does not inline so whatever happens inside it won't help the JIT improve the caller method in any way.

@benaadams
Copy link
Copy Markdown
Member Author

Sheesh, a never ending stream of hacks because the JIT can't figure things out.

Sounds like you are describing most of my PR aspnet/KestrelHttpServer#2347 😉

or as @halter73 refers to them "more esoteric parts [...] added to avoid extra bounds checks by the JIT."

A little bit here and there that the Jit misses does add up. The changes in the PR are a gain of 100K rps or a gain of the performance of an entire IIS server of the same spec O_o

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

A little bit here and there that the Jit misses does add up. The changes in the PR are a gain of 100K rps or a gain of the performance of an entire IIS server of the same spec O_o

Yeah. Now I haven't starred at everything to tell if it's feasible to handle everything in the JIT. But there definitely are things that the/a JIT could do if some of it's inner workings weren't so sloppy. And that's just disappointing.

@benaadams
Copy link
Copy Markdown
Member Author

asm changes

Total bytes of diff: -649 (-0.02% of base)
    diff is an improvement.

Total byte diff includes 0 bytes from reconciling methods
        Base had    0 unique methods,        0 unique bytes
        Diff had    0 unique methods,        0 unique bytes

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

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

Top method improvements by size (bytes):
         -64 : System.Private.CoreLib.dasm - Win32Marshal:GetExceptionForWin32Error(int,ref):ref
         -57 : System.Private.CoreLib.dasm - TimeZoneInfo:GetLocalizedNamesByRegistryKey(ref,byref,byref,byref)
         -32 : System.Private.CoreLib.dasm - String:Concat(ref,ref,ref,ref):ref
         -32 : System.Private.CoreLib.dasm - Path:CombineInternal(ref,ref,ref,ref):ref
         -24 : System.Private.CoreLib.dasm - String:Concat(ref,ref):ref (2 methods)
         -24 : System.Private.CoreLib.dasm - String:Concat(ref,ref,ref):ref (2 methods)
         -24 : System.Private.CoreLib.dasm - Path:CombineInternal(ref,ref,ref):ref
         -24 : System.Private.CoreLib.dasm - CalendarData:.ctor(ref,ushort,bool):this
         -23 : System.Private.CoreLib.dasm - Environment:SetEnvironmentVariableCore(ref,ref)
         -22 : System.Private.CoreLib.dasm - String:MakeSeparatorList(ref,byref,byref):this
         -17 : System.Private.CoreLib.dasm - PathInternal:NormalizeDirectorySeparators(ref):ref
         -16 : System.Private.CoreLib.dasm - Path:CombineInternal(ref,ref):ref
         -16 : System.Private.CoreLib.dasm - PathInternal:EqualStartingCharacterCount(ref,ref,bool):int
         -16 : System.Private.CoreLib.dasm - CalendarData:InitializeEraNames(ref,ushort):this
         -16 : System.Private.CoreLib.dasm - CalendarData:InitializeAbbreviatedEraNames(ref,ushort):this
         -16 : System.Private.CoreLib.dasm - ContractHelper:GetDisplayMessage(int,ref,ref):ref
         -11 : System.Private.CoreLib.dasm - CoreLib:FixupCoreLibName(ref):ref
         -11 : System.Private.CoreLib.dasm - String:Concat(ref):ref (5 methods)
         -11 : System.Private.CoreLib.dasm - CultureData:get_SENGDISPLAYNAME():ref:this
         -11 : System.Private.CoreLib.dasm - CultureData:get_SNATIVEDISPLAYNAME():ref:this
          -8 : System.Private.CoreLib.dasm - AppContextDefaultValues:TryParseFrameworkName(ref,byref,byref,byref):bool
          -8 : System.Private.CoreLib.dasm - Environment:ValidateVariableAndValue(ref,byref)
          -8 : System.Private.CoreLib.dasm - ArgumentException:get_Message():ref:this
          -8 : System.Private.CoreLib.dasm - Contract:AssertMustUseRewriter(int,ref)
          -8 : System.Private.CoreLib.dasm - Contract:ReportFailure(int,ref,ref,ref)
          -8 : System.Private.CoreLib.dasm - EventPipeProviderConfiguration:.ctor(ref,long,int):this
          -8 : System.Private.CoreLib.dasm - EventPipeConfiguration:.ctor(ref,int):this
          -8 : System.Private.CoreLib.dasm - Path:GetRelativePath(ref,ref,int):ref
          -8 : System.Private.CoreLib.dasm - PathInternal:EndsWithPeriodOrSpace(ref):bool
          -8 : System.Private.CoreLib.dasm - CultureInfo:get_Parent():ref:this
          -8 : System.Private.CoreLib.dasm - CultureData:GetCultureData(int,bool):ref
          -8 : System.Private.CoreLib.dasm - CultureData:get_TimeSeparator():ref:this
          -8 : System.Private.CoreLib.dasm - CultureData:GetNFIValues(ref):this
          -8 : System.Private.CoreLib.dasm - NumberFormatInfo:.ctor(ref):this
          -8 : System.Private.CoreLib.dasm - SystemTypeMarshaler:ConvertToManaged(long,byref)
          -8 : System.Private.CoreLib.dasm - StringBuilder:Insert(int,ref,int):ref:this
          -8 : System.Private.CoreLib.dasm - AssemblyLoadContext:ValidateAssemblyNameWithSimpleName(ref,ref):ref:this
          -8 : System.Private.CoreLib.dasm - ExternalException:ToString():ref:this
          -8 : System.Private.CoreLib.dasm - ContractHelper:TriggerFailure(int,ref,ref,ref,ref)
          -8 : System.Private.CoreLib.dasm - TypeForwardedFromAttribute:.ctor(ref):this
          -4 : System.Private.CoreLib.dasm - ResourceManager:GetStringFromPRI(ref,ref,ref):ref:this
          -2 : System.Private.CoreLib.dasm - String:IsNullOrEmpty(ref):bool
          -2 : System.Private.CoreLib.dasm - Environment:SetEnvironmentVariableCore(ref,ref,int)
          -2 : System.Private.CoreLib.dasm - TimeZoneInfo:TryGetLocalizedNameByMuiNativeResource(ref):ref
          -2 : System.Private.CoreLib.dasm - CultureData:GetCultureDataForRegion(ref,bool):ref
          -2 : System.Private.CoreLib.dasm - CultureData:GetCultureData(ref,bool):ref
          -2 : System.Private.CoreLib.dasm - CultureData:get_SLOCALIZEDDISPLAYNAME():ref:this
          -2 : System.Private.CoreLib.dasm - CultureData:get_IsInvariantCulture():bool:this
          -2 : System.Private.CoreLib.dasm - Assembly:LoadFromResolveHandler(ref,ref):ref
          -2 : System.Private.CoreLib.dasm - WindowsRuntimeMetadata:OnDesignerNamespaceResolveEvent(ref,ref):ref

50 total methods with size differences (50 improved, 0 regressed), 17247 unchanged.

@benaadams
Copy link
Copy Markdown
Member Author

Looking at String:Concat(ref,ref):ref the changes look like

 G_M59730_IG02:
        test     rdi, rdi
        je       SHORT G_M59730_IG03
        cmp      dword ptr [rdi+8], 0
-       sete     al
-       movzx    rax, al
-       test     eax, eax
-       je       SHORT G_M59730_IG08
+       ja       SHORT G_M59730_IG08
 G_M59730_IG03:
        test     rsi, rsi
        je       SHORT G_M59730_IG04
        cmp      dword ptr [rsi+8], 0
-       sete     al
-       movzx    rax, al
-       test     eax, eax
-       je       SHORT G_M59730_IG06
+       ja       SHORT G_M59730_IG06
 G_M59730_IG04:
        mov      rax, qword ptr [(reloc)]
        mov      rax, gword ptr [rax]

-; Total bytes of code 168, prolog size 8 for method String:Concat(ref,ref):ref
+; Total bytes of code 144, prolog size 8 for method String:Concat(ref,ref):ref

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 11, 2018

Looking at String:Concat(ref,ref):ref the changes look like

So basically the improvement is due to the ? true : false hack 😁

@stephentoub
Copy link
Copy Markdown
Member

A little bit here and there that the Jit misses does add up. The changes in the PR are a gain of 100K rps or a gain of the performance of an entire IIS server of the same spec O_o

@benaadams, I see changes in that PR besides ones to work around things the JIT could have done. How much of that 100K rps is due to which changes?

@benaadams
Copy link
Copy Markdown
Member Author

I see changes in that PR besides ones to work around things the JIT could have done. How much of that 100K rps is due to which changes?

About 50% from skipping struct copies and some better implementations; and 50% from bounds checks (per host header char)

@benaadams
Copy link
Copy Markdown
Member Author

benaadams commented Apr 11, 2018

Single threaded; focused microbenchmark, dev machine

Inital

               Method |       Mean |        Op/s | Scaled | Allocated |
--------------------- |-----------:|------------:|-------:|----------:|
 PlaintextTechEmpower |   717.2 ns | 1,394,376.1 |   1.00 |     344 B |
           LiveAspNet | 1,517.2 ns |   659,095.9 |   2.12 |    1056 B |

Skip struct copies (improve implementations)

               Method |       Mean |        Op/s | Scaled | Allocated |
--------------------- |-----------:|------------:|-------:|----------:|
 PlaintextTechEmpower |   695.1 ns | 1,438,728.6 |   1.00 |     344 B |
           LiveAspNet | 1,507.1 ns |   663,510.4 |   2.17 |    1056 B |

plus bounds check eliminations

               Method |       Mean |        Op/s | Scaled | Allocated |
--------------------- |-----------:|------------:|-------:|----------:|
 PlaintextTechEmpower |   680.0 ns | 1,470,672.2 |   1.00 |     344 B |
           LiveAspNet | 1,482.2 ns |   674,676.6 |   2.18 |    1056 B |

Actual server on network

[07:39:07.796] SDK:                         2.2.0-preview1-007522
[07:39:07.796] Runtime:                     2.1.0-preview2-26403-06
[07:39:07.796] ASP.NET Core:                2.1.0-preview3-32183

Inital

[07:40:36.895] RequestsPerSecond:           1918968
[07:40:36.895] Latency on load (ms):        1.7
[07:40:36.895] Max CPU (%):                 91
[07:40:36.895] WorkingSet (MB):             383
[07:40:36.896] Startup Main (ms):           422
[07:40:36.896] First Request (ms):          159.5
[07:40:36.896] Latency (ms):                0.7
[07:40:36.896] Socket Errors:               0
[07:40:36.896] Bad Responses:               0

After PR

[07:39:07.795] RequestsPerSecond:           2010354
[07:39:07.795] Latency on load (ms):        2.1
[07:39:07.795] Max CPU (%):                 91
[07:39:07.795] WorkingSet (MB):             371
[07:39:07.795] Startup Main (ms):           423
[07:39:07.796] First Request (ms):          157.7
[07:39:07.796] Latency (ms):                0.7
[07:39:07.796] Socket Errors:               0
[07:39:07.796] Bad Responses:               0

@stephentoub
Copy link
Copy Markdown
Member

About 50% from skipping struct copies and some better implementations; and 50% from bounds checks (per host header char)

Thanks. So if I'm reading the numbers correctly, if the JIT had been able to do a better job on bounds check removal, that would have improved throughput by ~2.5%, correct?

@benaadams
Copy link
Copy Markdown
Member Author

if the JIT had been able to do a better job on bounds check removal, that would have improved throughput by ~2.5%, correct?

Yes

Looking at String:Concat(ref,ref):ref the changes look like

So basically the improvement is due to the ? true : false hack 😁

True 😄 But if you next action was to then check the first char of the non-empty string, it now eliminates that bounds check 😉

@benaadams
Copy link
Copy Markdown
Member Author

benaadams commented Apr 11, 2018

if the JIT had been able to do a better job on bounds check removal, that would have improved throughput by ~2.5%, correct?

Yes

What its doing per loop is pretty light (char validation) so the range check is a significant portion of it

Though you'd get a similar effect if you searched for a char in a non-unsafe way in a string; not starting a zero offset and not ending at length

public int IndexOf(char c, String s, int offset, int count)
{
    var endPos = offset + count;
    var i = offset;
    for (; i < endPos; i++)
    {
       if (s[i] == c) break;   
    }
    return i == endPos ? -1 : i;
}
; loop start 
L005a: cmp eax, [r8+0x8]   ; range check
L005e: jae L008b           ; out of bounds
L0060: movsxd r9, eax                 ; s[i]
L0063: movzx r9d, word [r8+r9*2+0xc]  ; s[i]
L0069: movzx r10d, dx                 ; s[i]
L006d: cmp r9d, r10d        ; check char
L0070: jz L0078             ; found, exit
L0072: inc eax              ; i++
L0074: cmp eax, ecx         ; i < endPos
L0076: jl L005a             ; loop repeat

Where a lot more (half?) could be hoisted out of the loop

@AndyAyersMS
Copy link
Copy Markdown
Member

Thanks for the example. It is important for us to see cases where even the small details matter.

@benaadams
Copy link
Copy Markdown
Member Author

Realized I should validate the range so it could eliminate the bounds check

public int IndexOf(char c, String s, int offset, int count) 
{
    var endPos = offset + count;
    if ((uint)offset >= (uint)s.Length || (uint)count >= (uint)(s.Length - offset))
        throw new ArgumentOutOfRangeException();
    
    var i = offset;
    for (; i < endPos; i++)
    {
       if (s[i] == c) break;   
    }
    return i == endPos ? -1 : i;
}

Same result

; loop start 
L0072: cmp eax, r10d   ; range check   (validated by arg check)
L0075: jae L00c7       ; out of bounds (validated by arg check)
L0077: movsxd r9, eax                 ; s[i] Needs 3 movs?
L007a: movzx r9d, word [r8+r9*2+0xc]  ; s[i] some could
L0080: movzx r11d, dx                 ; s[i] be hoisted?
L0084: cmp r9d, r11d   ; check char
L0087: jz L008f        ; found, exit
L0089: inc eax         ; i++
L008b: cmp eax, ecx    ; i < endPos
L008d: jl L0072        ; loop repeat

Top half of the loop body could be changed to a single mov?

// value.Length == 0 as it will elide the bounds check to
// the first char: value[0] if that is performed following the test
// for the same test cost.
return (value == null || 0u >= (uint)value.Length) ? true : false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The comment explains the use of 0u, but why use the (redundant) ternary operator instead of:

return (value == null || 0u >= (uint)value.Length);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

#17512 (comment)

That does this change

        cmp      dword ptr [rsi+8], 0
-       sete     al
-       movzx    rax, al
-       test     eax, eax

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That does this change

Thanks! I think we should highlight that in the comments too (to avoid accidentally reverting it in the future).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mikedn do you have an issue # for this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added comment

/// It can be used for pinning and is required to support the use of span within a fixed statement.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public unsafe ref T GetPinnableReference() => ref (_length != 0) ? ref _pointer.Value : ref Unsafe.AsRef<T>(null);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rebase?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

bleh

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Had rebased to do asm diffs against master; forgot so it made the sync bad

@benaadams
Copy link
Copy Markdown
Member Author

@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Ubuntu x64 Checked corefx_baseline

@benaadams
Copy link
Copy Markdown
Member Author

That overtriggered CI O_o

@jkotas is this change ok for 2.1?

Copy link
Copy Markdown

@CarolEidt CarolEidt left a comment

Choose a reason for hiding this comment

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

This change seems quite safe, but I'm assuming that we will defer to 2.2.
@benaadams @stephentoub @mikedn @AndyAyersMS please speak up if you think it should be in 2.1.

@CarolEidt CarolEidt added this to the 2.2.0 milestone Apr 12, 2018
@benaadams
Copy link
Copy Markdown
Member Author

I'd love it to be in 2.1 as Kestrel has the exact pattern this would effect:

if (string.IsNullOrEmpty(hostText))
{
    return true;
}

if (hostText[0] == '[')
{
    return IsValidIPv6Host(hostText);
}

/cc @halter73 @davidfowl

@CarolEidt
Copy link
Copy Markdown

I'd love it to be in 2.1

OK, I'll let @ahsonkhan or @stephentoub make that call.

@mikedn
Copy link
Copy Markdown

mikedn commented Apr 12, 2018

I really don't like this kind of code contortions but the problem avoided by ? true : false has existed for so long and I'd guess that it won't be fixed anytime soon so I'm inclined to say "let's do this". The range check part is probably much easier to fix in the JIT but it's way too late for that now.

@benaadams
Copy link
Copy Markdown
Member Author

The range check part is probably much easier to fix in the JIT but it's way too late for that now.

Changing IsNullOrEmpty in coreclr keeps the more unusual code close to the JIT; rather than having it up stream and then being cargoculted in the wild; so its easier to revert when the JIT covers it.

Copy link
Copy Markdown
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

I do not like hacks like these either. This one is simple enough and local enough that it is ok to take for 2.1.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants