-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Support faster null checks #21765
Support faster null checks #21765
Conversation
|
@dotnet-bot test Ubuntu16.04 arm64 Cross Checked Innerloop Build and Test |
|
|
|
I do not think it is worth it to inline the reference check for strings. It is pretty rare to compare interned strings in the real world code. String comparisons are very common. Adding code bloat to every place that compares strings did not seem to be a good idea when I have looked into it some time ago. |
ef18b3c to
5e8d500
Compare
|
Dropped the string change |
|
Infra error @dotnet-bot test Windows_NT x64 full_opt ryujit CoreCLR Perf Tests Correctness |
|
Null check goes from G_M6073_IG02:
488BCF mov rcx, rdi
33D2 xor rdx, rdx
E800000000 call MethodInfo:op_Equality(ref,ref):bool
85C0 test eax, eax
0F8500000000 jne G_M6073_IG16To G_M6076_IG02:
4885FF test rdi, rdi
0F94C1 sete cl
0FB6C9 movzx rcx, cl
85C9 test ecx, ecx
0F8500000000 jne G_M6076_IG16Looking to see if can skip the extra |
|
Shouldn't this have to be tackled entirely at JIT level? Sounds to me that these mutations could be done for all code as the pattern is pretty straightforward. Cc @AndyAyersMS |
There's a bug for it #914 With the addition of a16d5dd an example asm change is: G_M6073_IG02:
488BCF mov rcx, rdi
33D2 xor rdx, rdx
E800000000 call MethodInfo:op_Equality(ref,ref):bool
85C0 test eax, eax
0F8500000000 jne G_M6073_IG16
G_M6073_IG03:
33DB xor ebx, ebx
488BEF mov rbp, rdi
4885ED test rbp, rbp
740F je SHORT G_M6073_IG04
488D0D00000000 lea rcx, [(reloc)]
48394D00 cmp qword ptr [rbp], rcx
7402 je SHORT G_M6073_IG04
33ED xor rbp, rbp
G_M6073_IG04:
488BCD mov rcx, rbp
33D2 xor rdx, rdx
E800000000 call MethodInfo:op_Equality(ref,ref):bool
85C0 test eax, eax
0F8487000000 je G_M6073_IG09
488BEF mov rbp, rdi
4885ED test rbp, rbp
740F je SHORT G_M6073_IG05
488D0D00000000 lea rcx, [(reloc)]
48394D00 cmp qword ptr [rbp], rcx
7402 je SHORT G_M6073_IG05
33ED xor rbp, rbp
G_M6073_IG05:
488BCD mov rcx, rbp
33D2 xor rdx, rdx
E800000000 call MethodInfo:op_Equality(ref,ref):bool
85C0 test eax, eax
0F852C000000 jne G_M6073_IG17
G_M6073_IG06:
4C8B7538 mov r14, gword ptr [rbp+56]
4D85F6 test r14, r14
743C je SHORT G_M6073_IG08
498BCE mov rcx, r14
E800000000 call RuntimeTypeHandle:HasInstantiation(ref)To G_M6076_IG02:
4885FF test rdi, rdi
0F8400000000 je G_M6076_IG15
33DB xor ebx, ebx
488BEF mov rbp, rdi
488D0D00000000 lea rcx, [(reloc)]
48394D00 cmp qword ptr [rbp], rcx
7402 je SHORT G_M6076_IG03
33ED xor rbp, rbp
G_M6076_IG03:
4885ED test rbp, rbp
0F8589010000 jne G_M6076_IG13
G_M6076_IG04:
488BEF mov rbp, rdi
488D0D00000000 lea rcx, [(reloc)]
48394D00 cmp qword ptr [rbp], rcx
7402 je SHORT G_M6076_IG05
33ED xor rbp, rbp
G_M6076_IG05:
4885ED test rbp, rbp
0F842C000000 je G_M6076_IG16
4C8B7538 mov r14, gword ptr [rbp+56]
4D85F6 test r14, r14
0F8448010000 je G_M6076_IG12
498BCE mov rcx, r14
E800000000 call RuntimeTypeHandle:HasInstantiation(ref):bool |
src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs
Outdated
Show resolved
Hide resolved
I don't see how the JIT could now that |
Ah, that thing. Maybe I should take a look at that again, perhaps there's some easy way to handle at least some of these cases. Though I'd guess that there is not... |
The special case where |
Not if you've overriden |
How? If you have something like if (left is null)
return right is null;
else
return left.Equals(right);Pretty much the only thing that the JIT can reasonably do is if (left is null)
return true;
else
return left.Equals(right);It obviously cannot do if (true)
return left is null;
else
return left.Equals(right);That would result in if (true)
return left is null;
else
return right.Equals(left);It still won't call The only way the JIT could do this is if it does interprocedural analysis to prove that It may also do this if |
|
The more I look at this, the more weird it seems. Because the natural implementation of if (left is null)
return right is null;
else
return left.Equals(right);and what it being done here is some rather special casing. And only because people can't just use And contorting the code (especially when done on such a large scale) or asking the JIT to do the impossible aren't exactly solutions. |
Changing to Though |
I know. But that's for ALL callsites. Are all relevant to performance? I'd bet no. |
83947e6 to
ece23d2
Compare
|
rebased |
|
|
37 bytes on -; Total bytes of code 5026, prolog size 64 for method RuntimeType:InvokeMember(ref,int,ref,ref,ref,ref,ref,ref):ref:this
+; Total bytes of code 5063, prolog size 64 for method RuntimeType:InvokeMember(ref,int,ref,ref,ref,ref,ref,ref):ref:thisChanges are similar to G_M605_IG88:
mov rcx, gword ptr [rbp-80H]
xor rdx, rdx
call MethodBase:op_Equality(ref,ref):bool
test eax, eax
jne G_M605_IG117To G_M612_IG99:
cmp gword ptr [rbp-80H], 0
je SHORT G_M612_IG100
xor ecx, ecx
jmp SHORT G_M612_IG101
G_M612_IG100:
mov ecx, 1
G_M612_IG101:
test ecx, ecx
jne G_M612_IG130
G_M612_IG102:
mov rcx, gword ptr [rbp-80H]The Should be something like? G_M612_IG99:
cmp gword ptr [rbp-80H], 0
+ je G_M612_IG130
- je SHORT G_M612_IG100
- xor ecx, ecx
- jmp SHORT G_M612_IG101
-G_M612_IG100:
- mov ecx, 1
-G_M612_IG101:
- test ecx, ecx
- jne G_M612_IG130
G_M612_IG102:
mov rcx, gword ptr [rbp-80H] |
I suppose you've reached the limits of what you can do with the |
|
Tried a few permutations, looks like best its going to go. Its mostly produces clean null checks; other than a couple odd places where it adds unnecessary extra compares and jmps #21765 (comment) which feels similar to https://github.com/dotnet/coreclr/issues/914 Further improvements could be: Avoid need for ternary true/false hack https://github.com/dotnet/coreclr/issues/914 (and weirdness around tests on inlined booleans above) Change call sites to use |
I'm looking into #914. Hopefully we can avoid this circus with |
src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs
Outdated
Show resolved
Hide resolved
|
@dotnet-bot test Windows_NT arm64 Cross Debug Innerloop Build |
|
@benaadams Thanks |
Signed-off-by: dotnet-bot <[email protected]>
Signed-off-by: dotnet-bot <[email protected]>
Signed-off-by: dotnet-bot <[email protected]>
Signed-off-by: dotnet-bot <[email protected]>
Signed-off-by: dotnet-bot <[email protected]>
Signed-off-by: dotnet-bot <[email protected]>
Signed-off-by: dotnet-bot <[email protected]>
Commit migrated from dotnet/coreclr@4e82a58

To improve the common use of right-sided null
x == nullupstream.If we trim most of the class
operator==methods to only null check (some also ReferenceEquals and check either arg fornull) then switch the common order of the params used for.Equals(which is normallyleft.Equals(right); so:Then it drops under the
[below ALWAYS_INLINE size]inlining threshold; after inlining the Jit can remove the branch (as right is constantnull) so it becomes justleft is nullA fast-path
.Equalsthat doesReferenceEqualsis then added so it doesn't inline for theis nullcheck; but does inline for a vs variable check (asbase.Equals(object)method is virtual and can't inline).Current
Previous PR #21736 (faster null checks)
This PR with both commits (operator== change +
ReferenceEqualsfast path in strongly typedEquals)/cc @jkotas @stephentoub better?
Contributes to https://github.com/dotnet/corefx/issues/34283#issuecomment-451015598