Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lifted comparison codegen can be improved #17261

Closed
VSadov opened this issue Feb 20, 2017 · 18 comments
Closed

lifted comparison codegen can be improved #17261

VSadov opened this issue Feb 20, 2017 · 18 comments
Labels
Area-Compilers Area-Performance Bug good first issue The issue is reserved for a first time, non-Microsoft contributor help wanted The issue is "up for grabs" - add a comment if you are interested in working on it Language-C#
Milestone

Comments

@VSadov
Copy link
Member

VSadov commented Feb 20, 2017

In the codegen of lifted comparison operators some conditional branches seem unnecessary.
They only serve to shortcircuit trivial bool operators over locals - that does not seem very profitable.

We should do something like in: dotnet/corefx#15941
( Also check dotnet/corefx#17535 if that is better )

Example:

We emit lifted equality for left and right as:

bool? result = (left.GetValueOrDefault() == right.GetValueOrDefault()) ?
                                                  ( left.HasValue == right.HasValue ) :
                                                   false;

A more efficient would be:

bool? result = (left.GetValueOrDefault() == right.GetValueOrDefault()) & 
                                           (left.HasValue == right.HasValue);
@gafter
Copy link
Member

gafter commented Feb 21, 2017

@VSadov Can you please spell out the issue (what kind of source, what IL do we generate now, and what IL would be better)?

@VSadov
Copy link
Member Author

VSadov commented Mar 1, 2017

added examples to the bug

@gafter gafter added this to the Unknown milestone Feb 9, 2018
@gafter gafter added help wanted The issue is "up for grabs" - add a comment if you are interested in working on it good first issue The issue is reserved for a first time, non-Microsoft contributor labels Feb 9, 2018
@isc30
Copy link
Contributor

isc30 commented Feb 26, 2018

In case no newcomer wants this one, I can take care

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Feb 26, 2018

This seems interesting to me. Though it seems like it could lead to worse performance in some cases. For example. if i have:

SomeLargeStruct? s1, s2;
//...
var b = s1 == s1;

Even if these have 'null' as their value, we'll incur the costly cost of doing the == check for the underlying values.

This is also surprising to me as i would not have expected the underlying == to be called if either value was null.

--

That said, if we think the null case is the rare case, then maybe it's better to be branchless and jsut do the work then try to bail out early if either value is null.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Feb 26, 2018

Hrmm. i don't repro this locally. The IL i see generated seems to be of the form:

var v = left.HasValue == right.HasValue
    ? left.HasValue ? left.GetValueOrDefault() == right.GetValueOrDefault()) : false
    : false;

Or is this only for the case of things like known primitive types? If so, then i can see how a branchless form could def make sense.

@VSadov
Copy link
Member Author

VSadov commented Feb 26, 2018

only for primitives

@CyrusNajmabadi
Copy link
Member

Gotcha. Then that makes a lot of sense!

@isc30
Copy link
Contributor

isc30 commented Mar 1, 2018

How can I see the generated IL for this? I mean, where is it being generated?

@Joe4evr
Copy link

Joe4evr commented Mar 1, 2018

@isc30 You should check out SharpLab.

@isc30
Copy link
Contributor

isc30 commented Mar 1, 2018

Thanks @Joe4evr

I'm getting this for built-in types:

left.GetValueOrDefault() == right.GetValueOrDefault()
    && left.HasValue == right.HasValue;

I wasn't able to reproduce any of your cases tho. If you could create an example in sharplab.io so we see what's the generated IL and how can be optimized would be great.

https://sharplab.io/#v2:CYLg1APgAgDABFAjAbgLACgoGYECY4DCcA3hnOXGRdnAEYD29ANnAEICuAlkwC4CSAOwAUAShJUKFTgJ4B+OJ3gBeOLjTpJASGlyFiOCoHsmTdZMkTzUAOwLlKziksBfS5ZoNmhJgEMAzn6i4hrm5EjwAMbKcABEAO70cTFmoXAAyjwATtIA5nAR+obGppYWIaE2+fb5TuVwrujOQA==

@svick
Copy link
Contributor

svick commented Mar 1, 2018

@isc30 That's the same as the example in the original post, since a && b is the same as a ? b : false.

If you look at the IL, you will see the unnecessary conditional branch as beq.s.

Basically, this issue is about changing from && to &.

@isc30
Copy link
Contributor

isc30 commented Mar 1, 2018

okay, this makes a lot of sense! thanks for the explanation

@alrz
Copy link
Contributor

alrz commented Mar 30, 2018

@svick
Copy link
Contributor

svick commented Mar 31, 2018

@alrz I still see && (and not &) in the decompiled C# (and beq.s in the IL), so I don't think this was fixed. What makes you think otherwise?

@alrz
Copy link
Contributor

alrz commented Mar 31, 2018

What makes you think otherwise?

My vision loss.

@alrz
Copy link
Contributor

alrz commented Mar 31, 2018

To be clear, the final codegen would look like this:

// We rewrite x == y as 
//
// tempx = x; 
// tempy = y;
// result = (tempx.GetValueOrDefault() == tempy.GetValueOrDefault()) &
//          (tempx.HasValue == tempy.HasValue);
//
// and x != y as
//
// tempx = x; 
// tempy = y;
// result = (tempx.GetValueOrDefault() != tempy.GetValueOrDefault()) ||
//          (tempx.HasValue != tempy.HasValue);
//
// Otherwise, we rewrite x OP y as
//
// tempx = x;
// tempy = y;
// result = (tempx.GetValueOrDefault() OP tempy.GetValueOrDefault()) &&
//          (tempx.HasValue & tempy.HasValue);
//

@VSadov can you confirm?

@isc30
Copy link
Contributor

isc30 commented Apr 3, 2018

why || and not |?

@VSadov
Copy link
Member Author

VSadov commented Apr 5, 2018

@alrz @isc30 - yes like that, and yes with regular | and & - to be branchless.

It also makes sense to use DeMorgan dual of the #2 to avoid != . The IL emit will have to use ceq and negate for the !=, but the following will have to negate only once, so it is a bit more compact:

// and x != y as
//
// tempx = x; 
// tempy = y;
// result = !((tempx.GetValueOrDefault() == tempy.GetValueOrDefault()) &
//          (tempx.HasValue == tempy.HasValue));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Area-Performance Bug good first issue The issue is reserved for a first time, non-Microsoft contributor help wanted The issue is "up for grabs" - add a comment if you are interested in working on it Language-C#
Projects
None yet
Development

No branches or pull requests

7 participants