Daily Knowledge Drop
When using LINQ to retrieve a value from a collection, there are a number of different techniques - however not all are equal in terms of performance.
Today we'll explore the various methods and their comparative performance.
First performance
First, we'll look at the various ways to retrieve a single value using variations of First
, when multiple values match the condition:
// populate with 10000 values
int[] values = values = Enumerable.Range(0, 10000).ToArray();
// Only using First
var firstValue = values.First(v => v > 400);
// Only FirstOrDefault
var firstDefaultValue = values.FirstOrDefault(v => v > 400);
// Where and then First
var whereFirstValue = values.Where(v => v > 400).First();
In the above example, an array of 10000 items is populated, and when we look at three ways to find the First value greater than 400.
Benchmarking the above scenario using BenchmarkDotNet, the results are as follows (using .NET6):
Method | Mean | Error | StdDev | Ratio |
---|---|---|---|---|
First | 2,330.2 ns | 20.03 ns | 18.74 ns | 1.00 |
FirstOrDefault | 2,330.7 ns | 19.28 ns | 18.04 ns | 1.00 |
WhereFirst | 722.3 ns | 8.27 ns | 7.73 ns | 0.31 |
Interestingly, the Where().First()
approach is 3 times faster than First
or FirstOrDefault
.
The benchmarks were also performed using a List<int>
as well as an Array[100]
(with the condition being > 40). The results were roughly the same, with the WhereFirst
approach 2-3 times
faster than First or FirstOrDefault.
Single performance
Next, we'll look at the various ways to retrieve a single value using variations of First/Single
, when a single value matches the condition:
// populate with 10000 values
int[] values = values = Enumerable.Range(0, 10000).ToArray();
// Only using First
var firstValue = values.First(v => v == 450);
// Only FirstOrDefault
var firstDefaultValue = values.FirstOrDefault(v => v == 450);
// Only Single
var singleValue = values.Single(v => v == 450);
// Only SingleOrDefault
var singleOrDefaultValue = values.SingleOrDefault(v => v == 450);
// Where and then First
var whereFirstValue = values.Where(v => v == 450).First();
In the above example, an array of 10000 items is populated, and when we look at five ways to find the First or Single value equal to 450.
Benchmarking the above scenario using BenchmarkDotNet, the results are as follows (using .NET6):
Method | Mean | Error | StdDev | Ratio |
---|---|---|---|---|
FirstOneValue | 2,342.4 ns | 28.89 ns | 25.61 ns | 1.00 |
FirstOrDefaultOneValue | 2,341.8 ns | 21.82 ns | 19.34 ns | 1.00 |
SingleOneValue | 53,322.5 ns | 403.26 ns | 357.48 ns | 22.77 |
SingleOrDefaultOneValue | 55,948.0 ns | 463.01 ns | 433.10 ns | 23.87 |
WhereFirstOneValue | 698.9 ns | 5.24 ns | 4.90 ns | 0.30 |
As before, Where().First()
is the quickest approach being 3 times faster than First or FirstOrDefault, while using Single is 22 times slower
than First and approximately 75 times slower
than Where().First()
The benchmarks were also performed using a List<int>
with 10000 items - the results were roughly the same, with the WhereFirst
approach 2-3 times
faster than First or FirstOrDefault and 65 times faster
than Single.
However, when performed with Array[100]
(with the condition being = 45) the differences between the various methods is not as drastic:
Method | Mean | Error | StdDev | Ratio |
---|---|---|---|---|
FirstOneValue | 256.0 ns | 1.19 ns | 1.05 ns | 1.00 |
FirstOrDefaultOneValue | 258.0 ns | 2.02 ns | 1.89 ns | 1.01 |
SingleOneValue | 574.3 ns | 11.07 ns | 9.24 ns | 2.24 |
SingleOrDefaultOneValue | 529.4 ns | 3.23 ns | 3.02 ns | 2.07 |
WhereFirstOneValue | 107.5 ns | 0.30 ns | 0.25 ns | 0.42 |
Notes
We've looked at various techniques to get a value from an enumeration of varying sizes - in all use cases performed for this post Where().First()
was the fastest approach. but this might not always be the same for all possible use cases.
Keep in mind that the size of the collection, and the method used does have a performance impact (admittedly nanoseconds, but in the right hot path it could make a material difference) and the various methods should be benchmarked with your specific use case to determine the correct method.
References
LINQ optimizations in .NET can be surprising
Daily Drop 84: 30-05-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.