-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Improve CopySign performance for integer types #90970
Changes from 6 commits
271014a
630c704
e8cee13
1dd6e3d
12274ee
725bd61
fe5b230
64044bd
3e650ba
3c1be6d
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 |
---|---|---|
|
@@ -631,24 +631,11 @@ public static short Log2(short value) | |
/// <inheritdoc cref="INumber{TSelf}.CopySign(TSelf, TSelf)" /> | ||
public static short CopySign(short value, short sign) | ||
{ | ||
short absValue = value; | ||
|
||
if (absValue < 0) | ||
{ | ||
absValue = (short)(-absValue); | ||
} | ||
|
||
if (sign >= 0) | ||
if (value == short.MinValue && sign >= 0) | ||
{ | ||
if (absValue < 0) | ||
{ | ||
Math.ThrowNegateTwosCompOverflow(); | ||
} | ||
|
||
return absValue; | ||
Math.ThrowNegateTwosCompOverflow(); | ||
} | ||
|
||
return (short)(-absValue); | ||
return (short)(value * Math.SignZeroToOne(value ^ sign)); | ||
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. Multiplication is much more expensive on older CPUs and may significantly regress performance. It's not necessarily "cheap" on modern CPUs either (still a minimum of 3-4 cycles for 32-bit, and 3-6 cycles for 64-bit) and while reduction of branches can be goodness, it might hurt things overall and hinder JIT optimizations for some constants. |
||
} | ||
|
||
/// <inheritdoc cref="INumber{TSelf}.Max(TSelf, TSelf)" /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1468,6 +1468,27 @@ public static int Sign(float value) | |
throw new ArithmeticException(SR.Arithmetic_NaN); | ||
} | ||
|
||
// Helper functions for CopySign | ||
// >= 0: returns 1 | ||
// < 0: returns -1 | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
tannergooding marked this conversation as resolved.
Show resolved
Hide resolved
|
||
internal static int SignZeroToOne(int 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. This name isn't "clear" IMO and needs something that makes its meaning clear. 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. SignOrOneIfZero? |
||
{ | ||
return (value >> (32 - 2)) | 1; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
tannergooding marked this conversation as resolved.
Show resolved
Hide resolved
|
||
internal static int SignZeroToOne(nint value) | ||
{ | ||
return (int)(value >> (8 * nint.Size - 2)) | 1; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
tannergooding marked this conversation as resolved.
Show resolved
Hide resolved
|
||
internal static int SignZeroToOne(long value) | ||
{ | ||
return (int)(value >> (64 - 2)) | 1; | ||
} | ||
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. Casting to int32 will introduce unnecessary truncate&extend on 64 bit platform, but saves a move on 32 bit platform. Also note that we typically put these methods in System.Numerics.BitOperations. 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 think returning an int (Int32) for Sign is reasonable, because it is the common return type for sign functions. Also, using a shorter bit length than long (Int64) may provide optimization opportunities for subsequent multiplications. Regarding where to put these methods, I think Sign and Abs are closely related, so it makes sense to group them together. Besides, the original code already referenced some throw helper methods from Math, so moving them to System.Numerics.BitOperations would make the code more scattered. 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. In such primitive function, one instruction counts. The helper isn't exposed to public, so it should be most performant form. In case when needed, you can use We need to understand the performance better. |
||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static decimal Truncate(decimal d) | ||
{ | ||
|
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.
I'd just do this the simple/readable way as:
The JIT should take care of inlining the comparison and IsPositive check to give good codegen.