-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Improve performance of Convert.ToInt32(bool) and related functions #16138
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -191,5 +191,27 @@ static internal ref byte GetRawSzArrayData(this Array array) | |
| typeof(ArrayPinningHelper).ToString(); // Type used by the actual method body | ||
| throw new InvalidOperationException(); | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| static internal byte BoolToByteNonNormalized(bool value) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this method should be added to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was hoping that there might be some cases where RyuJIT might elide the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I already sent a PR to fix that specific case. The JIT already eliminates At the same time, |
||
| { | ||
| // The body of this function will be replaced by the EE. | ||
|
|
||
| // Per ECMA 335, Sec III.1.1.2, the Boolean data type occupies 1 byte | ||
| // of memory. Per Sec. I.8.2.2 and I.8.7, the Boolean data type is internally | ||
| // treated as a one-byte integer interchangeable with Byte / SByte. This | ||
| // means that "casting" a Boolean to a Byte is both verifiable and free. | ||
|
|
||
| // n.b. The return value of this method isn't limited to 0 or 1. It'll contain | ||
| // the raw value behind the input Boolean, and technically 'true' could correspond | ||
| // to any non-zero value. If the caller doesn't know where the Boolean value | ||
| // came from and needs to normalize 'true' to 1, the caller should instead use | ||
| // Convert.ToInt32(Boolean), which performs normalization. | ||
|
|
||
| // ldarg.0 | ||
| // ret | ||
|
|
||
| throw new InvalidOperationException(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can JIT optimize !! away? Is not it a bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, haven't checked but both the JIT and the C# compiler tend to assume that
boolis either 0 or 1. It's a bit of the bug since the IL spec doesn't actually guarantee that but then this behavior is old enough that it has become the norm.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked, this is not JIT's doing. The C# compiler optimizes away
!!.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was mistaken. The C# compiler optimizes away
!!. I think this behavior is correct for the reason @mikedn calls out: the C#!operator is not the same as the C!operator, and the user-observable side effect of double-notting a bool is to keep the original value. So this code isn't working around a bug as much as it is trying to make up for a missing language feature. I should reword the comment.Additionally, as far as I can tell the JIT doesn't actually have a concept of a
!operator; thenotopcode is instead equivalent to one's complement (~). So putting twonotopcodes into the stream doesn't have the desired effect either.I can't think of a more efficient way of performing this normalization. One option would be to not perform the normalization, but there's probably somebody somewhere who's somehow passing a true value other than 1 and relying on the existing logic.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The usual way to negate a 0/1 bool is
xor 1. Funnily enough, the C# compilers assumes 0/1 bools but fails to perform this particular optimization.I doubt that not performing the normalization would have any meaningful impact considering that both the JIT (and AFAIR the JIT does some rather dubious things in this area) and the C# compilers perform various other optimizations assuming 0/1 bools. The ship has sailed a long time ago.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. The C# spec does not explicitly define the value of
trueto any specific numerical value. However the C# compiler (as well as F#, VB, Cobol, etc ... every .NET language in existance) defines the literaltrueas the value1/ldc.i4.1.Scenarios where the literal
trueis imagined as any other numeric value are academic.Incorrect. The compiler assumes booleans can only be 1 or 0 and emits it's logic based on that. Sure there are times where C#'s emitted code will happen to also work with a 2+ boolean value but that is just coincidence, not design.
Note that the boolean type itself agrees with the C# compiler here on the numeric values of true and false. Both in the constants it defines internally and the values that are returned from TryParse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potentially academic, but still inline with the IL definition:
I do think it would be desirable, and within the realm of reason, to have the C# spec explicitly list the values it recognizes as true/false; otherwise, two independent language implementations may do it differently (one may do 0/1, like Roslyn; and the other may do 0/not 0, like the IL spec).
Also, while I still think fixing the C# spec to explicitly have the same definition of
true(that is,trueis a non-zero bit pattern) would be nice. It could also be considered a breaking change in the scenarios that @mikedn listed above, since the code may now execute differently from before.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C# language specification is not specific to any particular runtime platform. The only observable consequence of placing something like that in the specification would be the behavior you get when you overlay a bool and a byte in unsafe code. Specifying that would be a peculiar departure from the normal situation in which the specification avoids telling you in detail the way unsafe code works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikedn I was surprised to see that making this a bitwise operator is a Roslyn-specific behavior. Earlier versions of the C# compiler use short-circuiting logic. So this means technically there has already been a breaking change in the language, though the true number of people impacted is probably minimal. (Of course, there's still wonkiness with the other operators.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the JIT does it too. But I'm not sure when it started doing it.