-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Specialized pure Order/OrderDescending Distinct #120131
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
Changes from all commits
af3deba
e809cc0
bdab472
147fb53
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 |
---|---|---|
|
@@ -42,10 +42,17 @@ public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source) => | |
/// | ||
/// If comparer is <see langword="null"/>, the default comparer <see cref="Comparer{T}.Default"/> is used to compare elements. | ||
/// </remarks> | ||
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source, IComparer<T>? comparer) => | ||
TypeIsImplicitlyStable<T>() && (comparer is null || comparer == Comparer<T>.Default) ? | ||
new ImplicitlyStableOrderedIterator<T>(source, descending: false) : | ||
OrderBy(source, EnumerableSorter<T>.IdentityFunc, comparer); | ||
public static IOrderedEnumerable<T> Order<T>(this IEnumerable<T> source, IComparer<T>? comparer) | ||
{ | ||
if (TypeCanBePureOrdered<T>() && (comparer is null || comparer == Comparer<T>.Default)) | ||
{ | ||
return TypeIsImplicitlyStable<T>() ? | ||
new ImplicitlyStableOrderedIterator<T>(source, descending: false) : | ||
new PureOrderedIteratorImpl<T>(source, descending: false); | ||
} | ||
|
||
return OrderBy(source, EnumerableSorter<T>.IdentityFunc, comparer); | ||
} | ||
|
||
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) | ||
=> new OrderedIterator<TSource, TKey>(source, keySelector, null, false, null); | ||
|
@@ -87,10 +94,17 @@ public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> sourc | |
/// | ||
/// If comparer is <see langword="null"/>, the default comparer <see cref="Comparer{T}.Default"/> is used to compare elements. | ||
/// </remarks> | ||
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source, IComparer<T>? comparer) => | ||
TypeIsImplicitlyStable<T>() && (comparer is null || comparer == Comparer<T>.Default) ? | ||
new ImplicitlyStableOrderedIterator<T>(source, descending: true) : | ||
OrderByDescending(source, EnumerableSorter<T>.IdentityFunc, comparer); | ||
public static IOrderedEnumerable<T> OrderDescending<T>(this IEnumerable<T> source, IComparer<T>? comparer) | ||
{ | ||
if (TypeCanBePureOrdered<T>() && (comparer is null || comparer == Comparer<T>.Default)) | ||
{ | ||
return TypeIsImplicitlyStable<T>() ? | ||
new ImplicitlyStableOrderedIterator<T>(source, descending: true) : | ||
new PureOrderedIteratorImpl<T>(source, descending: true); | ||
} | ||
|
||
return OrderByDescending(source, EnumerableSorter<T>.IdentityFunc, comparer); | ||
} | ||
|
||
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => | ||
new OrderedIterator<TSource, TKey>(source, keySelector, null, true, null); | ||
|
@@ -148,6 +162,38 @@ internal static bool TypeIsImplicitlyStable<T>() | |
t = typeof(T).GetEnumUnderlyingType(); | ||
} | ||
|
||
return NonEnumTypeIsImplicitlyStable(t); | ||
} | ||
|
||
/// <summary>A type can be pure ordered when every single time equal elements is side by side: [ (someVal), (someVal), (otherVal), (otherVal) ] see: #120125</summary> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
internal static bool TypeCanBePureOrdered<T>() | ||
{ | ||
Type t = typeof(T); | ||
|
||
Type? nullableUnderlyingType = Nullable.GetUnderlyingType(t); | ||
if (nullableUnderlyingType != null) | ||
{ | ||
t = nullableUnderlyingType; | ||
} | ||
|
||
if (typeof(T).IsEnum) | ||
{ | ||
t = typeof(T).GetEnumUnderlyingType(); | ||
} | ||
Comment on lines
+174
to
+183
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 can have negative impact. It may break constant folding for 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. The equality methods in nullable only add the null check, which are pure, if it is not null there is a fallback to the equality methods of the internal type basically only the internal type of nullable matters |
||
|
||
return NonEnumTypeIsImplicitlyStable(t) || | ||
t == typeof(Half) || t == typeof(float) || | ||
t == typeof(double) || t == typeof(decimal) || | ||
t == typeof(string) || t == typeof(Guid) || | ||
t == typeof(DateTime) || t == typeof(DateTimeOffset) || | ||
t == typeof(TimeSpan) || t == typeof(TimeOnly) || | ||
t == typeof(DateOnly); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private static bool NonEnumTypeIsImplicitlyStable(Type t) | ||
{ | ||
// Check for integral primitive types that compare equally iff they have the same bit pattern. | ||
// bool is included because, even though technically it can have 256 different values, anything | ||
// other than 0/1 is only producible using unsafe code. It's tempting to include a type like string | ||
|
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.
A type can be pure ordered when the equality methods is pure, meaning for the same input always have the same output
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 don't think this is a correct application of the term "pure". A struct with two fields implementing IEquatable that only projects to one field is also pure in that sense but it is invalid from the perspective of this optimization. The defining property you seem to be testing for is implicit stability, so this is merely an extension of the existing
TypeIsImplicitlyStable
to more types.Could
!typeof(IEquatable<T>).IsAssignableFrom(typeof(T)) && !RuntimeHelpers.IsReferenceOrContainsReferences<T>()
be an acceptable proxy for widening this test to more types?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.
Answering my own question, probably not because
T
could contain fields that themselves implementIEquatable<T>
in an unstable manner. I don't think we need this method,TypeIsImplicitlyStable
is probably good enough, although we could have conversation about how much we can extend its scope.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 agree that "Pure" is not the best word to describe that, but that's the best name that I came up with
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 method TypeIsImplicitlyStable is a subset of what the optimization can handle
Example:
Although
firstElement.Equals(secondElement) == true
it needs to be in that order (stable)
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 implicitly stability is not important,
Consider this code:
dateTimeOffsets.Distinct()
Will return: DateTimeOffset.Parse("2025-09-29T12:00:00+00:00") (The first item)if the sorting wasn't stable the Distinct() can return other item
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.
Linq sorting is stable. I guess what I'm trying to hint at is that your change looks promising, but you need to better clarify the conditions for when the optimization kicks in.
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.
I think that's the conditions for this optimization
If the type is not implicitly stable, it should use a stable sort, otherwise it can use an unstable (doesn't matter)
When looping the ordered collection the current element (when is a sequence of equal elements like [1,1,1] is threated like a single element [1]) it not equals to any other past element
To achive that, the methods
GetHashCode
,CompareTo
,Equals
should be properly implemented"properly implemented" means that the methods must agree with each other
Like this example:
Other good example:
This would be invalid:
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're in agreement, but I would like you to update the name of your test method to better reflect what is being tested and potentially include even more types.
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.
I added more tests, I tried to improve the names, it's better, but I agree that they aren't the best names
And I remembered other 2 types to add to this optimization:
DateOnly
andTimeOnly
And about the failing tests in the ci, I believe that it's not related to this changes, all the logs says:
unable to pull image...