Skip to content

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

Merged
merged 7 commits into from
Aug 10, 2018

Conversation

powercode
Copy link
Collaborator

@powercode powercode commented Aug 3, 2018

PR Summary

This PR adds the TryCompare equivalent of Compare, just as we have a TryConvert alternative to Convert.
This enables faster comparison in the cases where the convertions throws exception.

As an example:

 1..10000 | foreach{$_; [timespan]::FromSeconds($_)}  | sort

takes 3 800 ms before and 280 ms after.

$b = 1..1000000 | %{100; [timespan]::FromSeconds(100)}  | group

takes 20 s before and 15 s after.

It is implemented equivalently to LanguagePrimitives.Compare, but returning false where Compare 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:

class LanguagePrimitives {
  public static bool TryCompare(object first, object second, out int result);
  public static bool TryCompare(object first, object second, bool ignoreCase, out int result);
  /// <summary>
  /// Tries to compare first and second, converting second to the type of the first, if necessary.
  /// If a conversion is needed but fails, false is return.
  /// </summary>
  /// <param name="first">First comparison value.</param>
  /// <param name="second">Second comparison value.</param>
  /// <param name="ignoreCase">Used if both values are strings.</param>
  /// <param name="formatProvider">Used in type conversions and if both values are strings.</param>
  /// <param name="result">Less than zero if first is smaller than second, more than  zero if it is greater or zero if they are the same.</param>
  /// <returns>True if the comparison was successful, false otherwise.</returns>
  /// <exception cref="ArgumentException">The parameter <paramref name="formatProvider"/> is not a <see cref="CultureInfo"/>.</exception>
  public static bool TryCompare(
      object first, 
      object second, 
      bool ignoreCase, 
      IFormatProvider formatProvider,
      out int result);
}

The new APIs are used by ObjectCommandPropertyValue (used by sort-object and group-object) and FormatGroupManager.

PR Checklist

@powercode
Copy link
Collaborator Author

The remaining CodeFactor issues are intentionally left that way to match the style of similar code in the same file.

@powercode powercode changed the title Adding LanguagePrimitives.TryCompare to provide faster comparisons WIP: Adding LanguagePrimitives.TryCompare to provide faster comparisons Aug 3, 2018
@powercode powercode force-pushed the FasterCompare branch 2 times, most recently from ae34a95 to 0553541 Compare August 3, 2018 06:48
@powercode powercode changed the title WIP: Adding LanguagePrimitives.TryCompare to provide faster comparisons Adding LanguagePrimitives.TryCompare to provide faster comparisons Aug 3, 2018
@iSazonov
Copy link
Collaborator

iSazonov commented Aug 5, 2018

This PR adds the Compare equivalent of TryConvert.

Seem typo in the PR description.

Copy link
Collaborator

@iSazonov iSazonov left a 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);
Copy link
Collaborator

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
Copy link
Collaborator

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))
Copy link
Collaborator

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?

Copy link
Collaborator Author

@powercode powercode Aug 6, 2018

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)
Copy link
Collaborator

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))
Copy link
Collaborator

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.

@iSazonov iSazonov added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Aug 5, 2018
@daxian-dbw daxian-dbw requested review from SteveL-MSFT and removed request for JamesWTruher and PaulHigin August 6, 2018 21:49
@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Aug 8, 2018
@SteveL-MSFT
Copy link
Member

@PowerShell/powershell-committee reviewed this and is fine with the api additions. Thanks.

Copy link
Collaborator

@iSazonov iSazonov left a 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);
Copy link
Collaborator

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?

Copy link
Collaborator Author

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)
Copy link
Collaborator

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.

Copy link
Member

@daxian-dbw daxian-dbw left a comment

Choose a reason for hiding this comment

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

LGTM

@daxian-dbw daxian-dbw merged commit 3aaa181 into PowerShell:master Aug 10, 2018
default: return -1;
}
}
return second == null ? 0 : CompareObjectToNull(second, true);
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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))
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

@PaulHigin
Copy link
Contributor

Whoops, I just saw that this has already been merged. None of my comments are blocking.

@powercode powercode deleted the FasterCompare branch August 10, 2018 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Committee-Reviewed PS-Committee has reviewed this and made a decision
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants