dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.17k stars 4.72k forks source link

something.Where(predicate).FirstOrDefault() is faster than something.FirstOrDefault(predicate) #100378

Open Treit opened 6 months ago

Treit commented 6 months ago

See the benchmark results here:

https://github.com/Treit/MiscBenchmarks/tree/main/WhereFirstOrDefaultVsFirstOrDefault

image

In a recent code review someone pointed out that we should change calls to something.Where(somePredicate).FirstOrDefault() to just be something.FirstOrDefault(somePredicate)

Much to my surprise, benchmarks show this is significantly slower, at least in the worst case where the thing being searched for is at the end of the list.

dotnet-policy-service[bot] commented 6 months ago

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch See info in area-owners.md if you want to be subscribed.

vcsjones commented 6 months ago

Duplicate of #30119 and https://github.com/dotnet/runtime/issues/19382#issuecomment-734524103

neon-sunset commented 6 months ago

Since then LINQ has received extensive optimizations. This issue effectively suggests to address the discrepancy between WhereListIterator specialization and the lack of its counterpart on FirstOrDefault.

In particular, it is an issue as the analyzer will actively suggest to simplify expressions of list.Where(condition).FirstOrDefault() to just list.FirstOrDefault(condition). Performance regression in this case will be unexpected and may lead to yet another case of "you have to know how to use the LINQ right", which may not be desirable.

Given that this will be guarded behind similar size opt flag like other iterators for size-sensitive targets, perhaps this can be reconsidered?

eiriktsarpalis commented 6 months ago

. This issue effectively suggests to address the discrepancy between WhereListIterator specialization and the lack of its counterpart on FirstOrDefault.

In particular, the perf improvement discrepancy comes from the WhereIterator types specializing for list and array types, but I don't believe it would be as pronounced if the source enumerable was anything else. The performance of First(OrDefault) taking predicates can be improved for those cases if we add detection for span-based sources, as shown in this draft PR.