-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Adding LanguagePrimitives.TryCompare to provide faster comparisons #7438
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
Conversation
d767118
to
296db72
Compare
The remaining CodeFactor issues are intentionally left that way to match the style of similar code in the same file. |
ae34a95
to
0553541
Compare
This can be used by OrderObjectBase to have a faster code path that doesn't throw exceptions.
0553541
to
1416a78
Compare
Seem typo in the PR description. |
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.
Please add some samples that we really get a performance win.
New public API must be approved by @powershell-committee.
/// <returns>True if the comparison was successful, false otherwise.</returns> | ||
public static bool TryCompare(object first, object second, out int result) | ||
{ | ||
return TryCompare(first, second, false, CultureInfo.InvariantCulture, out result); |
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.
Please add name for const argument ignoreCase: false
.
It "TryCompare should succeed on int and string" { | ||
$result = $null | ||
[System.Management.Automation.LanguagePrimitives]::TryCompare(1, "1", [ref] $result) | Should -BeTrue | ||
$result | Should -BeExactly 0 |
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.
We use -BeExactly
for string only. Here we expect an integer and should use -Be
.
Please review using -BeExactly
below too.
formatProvider = CultureInfo.InvariantCulture; | ||
} | ||
|
||
if (!(formatProvider is CultureInfo culture)) |
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.
Why we use IFormatProvider
parameter type if we want get CultureInfo?
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'm not inventing a new API here, just providing a Try
alternative to the existing one.
If that API should be changed seems like a separate discussion.
first = PSObject.Base(first); | ||
second = PSObject.Base(second); | ||
|
||
int CompareNullToNumeric(object value, bool numberIsRightHandSide) |
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 think we should use "dry" principle and use the function everywhere in the file.
{ | ||
if (!(second is string secondString)) | ||
{ | ||
if (!TryConvertTo(second, culture, out secondString)) |
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.
We could replace the two if-s with one.
@PowerShell/powershell-committee reviewed this and is fine with the api additions. Thanks. |
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.
LGTM with 3 minor comments.
Thanks for the great performance improvements.
{ | ||
// Note that this will occur if the objects do not support | ||
// IComparable. We fall back to comparing as strings. | ||
return result * (_ascendingOrder ? 1 : -1); |
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.
Do we get some win if we exclude multiplication?
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 leave that to the compiler and the JIT. They usually do a good job on things like this.
|
||
|
||
|
||
if (first == null && second == null) |
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.
Please remove extra empty lines above.
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.
LGTM
default: return -1; | ||
} | ||
} | ||
return second == null ? 0 : CompareObjectToNull(second, true); |
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.
Nit: CompareObjectToNull(second, numberIsRightHandSide:true)
case TypeCode.Decimal: return System.Math.Sign((System.Decimal)first) < 0 ? -1 : 1; | ||
default: return 1; | ||
} | ||
return CompareObjectToNull(first, false); |
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.
Nit: CompareObjectToNull(first, numberIsRightHandSide:false)
|
||
if (first == null) | ||
{ | ||
result = CompareObjectToNull(second, true); |
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.
Nit: CompareObjectToNull(second, numberIsRighthandSide:true)
{ | ||
// If it's a positive number, including 0, it's greater than null | ||
// for everything else it's less than zero... | ||
result = CompareObjectToNull(first, false); |
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.
Nit: Same comment
{ | ||
if (!(second is string secondString)) | ||
{ | ||
if (!TryConvertTo(second, culture, out secondString)) |
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.
TryConvertTo() uses ConversionData.Invoke() which throws exceptions for invalid casts. Have you considered adding a "TryInvoke" method to avoid the exception, for performance?
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.
@PaulHigin I recently changed TryConvertTo to check the result of FigureConvertion and exit early, without invoking ConversionData.Invoke() if the ConvertionRank is None.
So, yes, I have avoided the exception in many but not all cases. Interesting take to put it on ConversionData. That is arguably where it belongs.
Whoops, I just saw that this has already been merged. None of my comments are blocking. |
PR Summary
This PR adds the
TryCompare
equivalent ofCompare
, just as we have aTryConvert
alternative toConvert
.This enables faster comparison in the cases where the convertions throws exception.
As an example:
takes
3 800 ms
before and280 ms
after.takes
20 s
before and15 s
after.It is implemented equivalently to
LanguagePrimitives.Compare
, but returning false whereCompare
would have thrown an exception.This is adding a new public API to
LanguagePrimitives
, so it will probably need a committee approval.The new APIs are:
The new APIs are used by
ObjectCommandPropertyValue
(used by sort-object and group-object) andFormatGroupManager
.PR Checklist
.h
,.cpp
,.cs
,.ps1
and.psm1
files have the correct copyright headerWIP:
to the beginning of the title and remove the prefix when the PR is ready.[feature]
if the change is significant or affects feature tests