-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Array.FindAll improvements #120336
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
Array.FindAll improvements #120336
Conversation
…n just return the already allocated array
|
Thanks you for your contribution. The code is not performing well with best practices. Instead, just replace the |
|
There may be more unintentional affect after rethinking. Initializing the array pool for each |
|
What would be the typical distribution of the array lengths passed into this API and returned by this API in your case?
This type of change tends to be shifting the cost. It improves the cases that we expect to matter more, and degrades the cases that we expect to be unimportant. My gut-feel is that:
|
Does it make sense to check the length of the incoming array first? I presume that the dotnet runtime will move the unused code paths out of the way, when it is optimizing. |
I can no longer remember the context, but likely less than 4 and almost certainly less than 16 inputted. |
|
@jkotas would the following implementation match what you are suggesting? public static T[] FindAll<T>(T[] array, Predicate<T> match)
{
if (array == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (match == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
InlineArray4<T> stackAllocatedMatches = default;
Span<T> span = stackAllocatedMatches;
int foundCount = 0;
for (int i = 0; i < array.Length; i++)
{
T value = array[i];
if (match(value))
{
if (foundCount >= span.Length)
{
T[] values = new T[span.Length * 2];
span.CopyTo(values);
span = values;
}
span[foundCount++] = value;
}
}
return span.Slice(0, foundCount).ToArray();
} |
|
Yes, I think something like this would have a better balance of perf characteristics. You do not need to special case Array,Empty in the implementation. Span.ToArray has that special case already. |
|
@Henr1k80 could you please try the implementation #120336 (comment) and get the benchmark numbers for it? Thanks! |
This is a convenience API that is always going to be leaving perf on the table. I do not think it makes sense to overengineer the implementation like this. |
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.
@jkotas the latest update LGTM. Please let us know if you have any more feedback.
jkotas
left a comment
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 otherwise. Thanks!
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
|
I just updated the bench results in the original comment to the newest suggestions. |
I found that there were room for improvement in
Array.FindAllNow a small amount of matches are stack allocated and the List is only optionally allocated.
It is always faster with less allocations.
The stack allocated size can be made bigger, but it is fixed size, so it will have a price for no-match schenarios.
InlineArray16<T>is only in .NET 10, but if this should be backported to .NET 8, we can create our local InlineMatchArrayX