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 Jan 1, 2019

operator ==(MethodInfo left, MethodInfo right) was showing up in traces, ~40% of them from == null checks

This changes the null checks against (most) MemberInfo derived classes in coreclr to be an is null fast null check and related changes; rather than calling through to their operator overloads to test for null.

Mostly effects class types that overload the operator== and operator!= as these methods are then used for null.

Pre

image

Post

image

return false;
}

if (left is Type type1)
Copy link
Member

Choose a reason for hiding this comment

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

Should this rather call left.Equals(right) instead of this manual dispatch?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like Type is the only particularly special one where operator == is an external call and its .Equals test is a ReferenceEquals; so maybe have to manually dispatch for Type?

Copy link
Member

Choose a reason for hiding this comment

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

operator== is external call on Type just so the JIT can recognize it as intrinsic. It is from the days when the method had to be FCall in order to be recognized as intrinsic.

It should be fine to just call Equals for Type as well.

@jkotas
Copy link
Member

jkotas commented Jan 1, 2019

Nice! Have you done anything to find these automatically - should we do the same cleanup in other repos too?

@benaadams
Copy link
Member Author

Have you done anything to find these automatically

I'd like to say I have... that would, sound good 😅

@benaadams
Copy link
Member Author

benaadams commented Jan 1, 2019

Commit c0884ae is for isnot null checks t is object; because !(t is null) is ugly and C# doesn't have a better semantic.

Should be easier to search and replace the simple is object
than replacing the regex \!\(([a-zA-Z\[\]]+) is null\) when it does.

/cc @jaredpar

@benaadams
Copy link
Member Author

Going to go all in on this

@benaadams benaadams changed the title Faster null checks [WIP] Faster null checks Jan 2, 2019
@jkotas
Copy link
Member

jkotas commented Jan 2, 2019

t is object

This construct does not look anything like a null check. !(x is null) is more readable. I would optimize for readability at any given point, not for future find&replace that may or may not happen.

@benaadams benaadams changed the title [WIP] Faster null checks Faster null checks Jan 2, 2019
@jkotas
Copy link
Member

jkotas commented Jan 2, 2019

cc @stephentoub Do you have an opinion about doing this globally and about the pattern to use for the "is not null" case?

Copy link
Member

@gfoidl gfoidl left a comment

Choose a reason for hiding this comment

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

Not all changes are necessary, as the code gets less readable and the C# compiler emits the same code. See the two examples I've commented.

For the initial motivation with MethodInfo there is a difference, notably by casting to object and then performing the null-check instead of the (not inlined) method call.

try
{
if (path == null)
if (path is null)
Copy link
Member

Choose a reason for hiding this comment

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

Is here actually a difference as Roslyn generates the same code for both?

BTW: here I would use string.IsNullOrEmpty for this and the following check.

_currentlyLoading.Add(key); // Push

if (ResourceManager == null)
if (ResourceManager is null)
Copy link
Member

Choose a reason for hiding this comment

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

Is here actually a difference as Roslyn generates the same code for both?

Though I find this version more readable / nicer 😉.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can either pick consistency or ease of changing 4k+ call sites 😄

Early commits I was only picking a specific set of classes; doing it for the all == null and != null checks is either much slower or more broad brush (or would need time to write a tool)

Copy link
Member

Choose a reason for hiding this comment

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

That's right 😉

!(obj is null) vs. obj != null the latter is more readable.
obj is null vs obj == null I find both very readable.
So it's hard to find a good balance between consistency and readability, as for MethodInfo and the like the is-variant has to be taken perf-wise -- or adapt the compiler(s) to emit efficient code for the null-comparison.

Copy link
Member Author

@benaadams benaadams Jan 2, 2019

Choose a reason for hiding this comment

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

Was a discussion (on twitter) about a less fugly is not null check for C#; most popular variants seemed to be x !is null and x is !null

Don't know if there is a formal C# proposal though

Copy link
Member Author

Choose a reason for hiding this comment

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

VB's leading the way here by already having IsNot Nothing 😉

Copy link
Member

Choose a reason for hiding this comment

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

Too many channels to follow 😃 Thanks for the link!

Copy link
Member

Choose a reason for hiding this comment

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

formal proposal

It's there: dotnet/csharplang#27. Someone suggested aint operator as well. 😄

Choose a reason for hiding this comment

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

I think this is the current proposal that would end up landing the 'not' pattern: dotnet/csharplang#1350

Copy link

@Joe4evr Joe4evr Jan 2, 2019

Choose a reason for hiding this comment

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

For checking not null specifically, there's also the syntax that falls out from Property Patterns: if (foo is {}), though I will concede that it's not quite as readable as if (foo is not null), but the outcome is the exact same.

Copy link
Member Author

Choose a reason for hiding this comment

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

if (foo is object) also works, but undid that in e4389b0 as it wasn't very clear

@benaadams
Copy link
Member Author

@dotnet-bot test Ubuntu x64 Checked Innerloop Build and Test

@benaadams
Copy link
Member Author

@dotnet-bot test Ubuntu x64 Checked Innerloop Build and Test (Jit - TieredCompilation=0)

@stephentoub
Copy link
Member

@stephentoub Do you have an opinion about doing this globally and about the pattern to use for the "is not null" case?

If we're going to do it, we should do it everywhere / globally, across coreclr/corert/corefx, for both the is and is not cases. For is not, we should go with !(foo is null) until there's a better option (which foo is object is not 😄). There's unfortunately going to continue to be inconsistency even when we do that, in that == checks will still be used for comparisons other than explicit nulls, null comparisons are performed via other means like Interlocked.CompareExchange, etc.

The other option would be to never use foo is null, and instead explicitly use ReferenceEquals(foo, null) when we want to avoid == overloaded semantics. I don't have a strong opinion; I'm ok with the is null option.

@benaadams
Copy link
Member Author

To improve the common use of right-sided null x == null upstream.

If we trim most of the class operator== methods to only null check (some also ReferenceEquals and check either arg for null) then switch the common order of the params used for .Equals (which is normally left.Equals(right); so:

public static bool operator== (MyClass left, MyClass right)
    => (right is null) ? (left is null) : right.Equals(left);

Then it drops under the [below ALWAYS_INLINE size] inlining threshold; after inlining the Jit can remove the branch (as right is constant null) so it becomes just left is null

@jkotas
Copy link
Member

jkotas commented Jan 2, 2019

If we trim most of the class operator== methods to only null check (some also ReferenceEquals and check either arg for null) then switch the common order of the params used for .Equals

So would the whole perf benefit of this change come from doing just this?

@benaadams
Copy link
Member Author

So would the whole perf benefit of this change come from doing just this?

Just checking

@galich
Copy link

galich commented Jan 2, 2019

just 2 cents - i'd rather see compiler improved to support this x==null case instead of everyone changing their code

@benaadams
Copy link
Member Author

So would the whole perf benefit of this change come from doing just this?

Works even better; every call evaporates even the non null tests #21765

@benaadams benaadams closed this Jan 2, 2019
@benaadams benaadams deleted the null-checks branch January 3, 2019 10:58
if (v == null)
if (!(value is MulticastDelegate v))
return this;
if (v._invocationList as object[] == null)
Copy link

Choose a reason for hiding this comment

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

Why is == null used here?

if (argumentType is null)
throw new ArgumentNullException(nameof(argumentType));

m_value = (value == null) ? null : CanonicalizeValue(value);
Copy link

Choose a reason for hiding this comment

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

Should is be used here?

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.

9 participants