In todays‘ Twitter post (unfortunately, I lost the link and I don’t find anything in Twitter), I saw the recommendation to use the following pattern.
Don’t use
if (String.IsNullOrEmpty(text))
Use:
if(text is {Length: 0})
Neglecting the style itself, I was heavily interested to understand the runtime impact of this style.
BenchmarkDotNet is quite comfortable here…. But not talking to much about that… Here is the Benchmark Code, just for the pattern:
Static Scenario
[Benchmark()] public int StringIsLength() { var result = 0; for (var n = 0; n < 10000; n++) { var x = ""; if (x is { Length: 0 }) { result++; } x = "a"; if (x is { Length: 0 }) { result++; } } return result; }
Iteration by 10.000 was needed due to the following result. The IL language looks like the following:
// [49 13 - 49 41] // String.IsNullOrEmpty
IL_001b: call bool [System.Runtime]System.String::IsNullOrEmpty(string)
IL_0020: brfalse.s IL_0026
versus
// [17 13 - 17 36] // x is {Length:0}
IL_000c: ldloc.2 // x
IL_000d: brfalse.s IL_001b
IL_000f: ldloc.2 // x
IL_0010: callvirt instance int32 [System.Runtime]System.String::get_Length()
IL_0015: brtrue.s IL_001b
It heavily now depends on the implementation of get_Length and String.IsNullOrEmpty…
// .Net Code
public static bool IsNullOrEmpty(string? value)
{
return (value == null || 0 == value.Length);
}
So… looks like x is {Length: 0} can win because it avoids an indirect jump…
The results:
Method | Mean | Error | StdDev | Code Size |
---|---|---|---|---|
StringIsLength | 5.800 us | 0.0650 us | 0.0508 us | 65 B |
StringIsNullOrEmpty | 3.131 us | 0.0077 us | 0.0068 us | 17 B |
StringIsNullOrEmpty is faster and smaller… What happened?
Looking at the disassembly of x is {Length:0}
## .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
```assembly
; Program.StringIsLength()
; var result = 0;
; ^^^^^^^^^^^^^^^
xor eax,eax
; for (var n = 0; n < 10000; n++)
; ^^^^^^^^^
xor edx,edx
mov rcx,1F848B23020
mov rcx,[rcx]
mov r8,1F848B2A110
mov r8,[r8]
; var x = "";
; ^^^^^^^^^^^
M00_L00:
mov r9,rcx
; if (x is { Length: 0 }) { result++; }
; ^^^^^^^^^^^^^^^^^^^^^^^
cmp dword ptr [r9+8],0
jne short M00_L01
inc eax
; x = "a";
; ^^^^^^^^
M00_L01:
mov r9,r8
; if (x is { Length: 0 }) { result++; }
; ^^^^^^^^^^^^^^^^^^^^^^^
cmp dword ptr [r9+8],0
jne short M00_L02
inc eax
M00_L02:
inc edx
cmp edx,2710
jl short M00_L00
ret
; Total bytes of code 65
As expected… Now String.IsNullOrEmpty
## .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
```assembly
; Program.StringIsNullOrEmpty()
; var result = 0;
; ^^^^^^^^^^^^^^^
xor eax,eax
; for (var n = 0; n < 10000; n++)
; ^^^^^^^^^
xor edx,edx
; result++;
; ^^^^^^^^^
M00_L00:
inc eax
inc edx
cmp edx,2710
jl short M00_L00
ret
; Total bytes of code 17
??! It just counts from 0 to 10.000 and returns the increase… The jitter just strips of the String.IsNullOrEmpty call and returns the result… It knows, that String.IsNullOrEmpty(„“) is false and String.IsNullOrEmpty(„a“) is true. (Ok, the jitter could also optimize the loop since the result is static)
Conclusion
String.IsNullOrEmpty is faster in static scenarios, now changing to dynamic scenarios. Here, something else will be figured out
Dynamic Scenario
Method | Mean | Error | StdDev | Code Size |
---|---|---|---|---|
StringIsLengthDynamic | 6.949 us | 0.0178 us | 0.0157 us | 104 B |
StringIsNullOrEmptyDynamic | 6.942 us | 0.0229 us | 0.0215 us | 104 B |
Same performance!! 😀 (Code size change is due to some strange stuff caused by BenchmarkDotNet)?
empty = ""; nonEmpty = "a"; [Benchmark()] public int StringIsLengthDynamic() { var result = 0; for (var n = 0; n < 10000; n++) { if (empty is { Length: 0 }) { result++; } if (nonEmpty is { Length: 0 }) { result++; } } return result; }
Bäng…
; if (nonEmpty is { Length: 0 }) { result++; }
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
M00_L01:
mov rdx,238B4367348
mov rdx,[rdx]
test rdx,rdx
je short M00_L02
cmp dword ptr [rdx+8],0
jne short M00_L02
vs
; if (string.IsNullOrEmpty(empty)) { result++; }
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
M00_L00:
mov rdx,rax
test rdx,rdx
je short M00_L01
cmp dword ptr [rdx+8],0
jne short M00_L02,
Conclusio
It does not matter which style to use for performance reasons… It just depends upon which style you prefer. The jitter is aware to optimize String.IsNullOrEmpty.
Here is everything: stringisnullorempty.asm (github.com)