I have a sequence of items represented by IEnumerable
I need to loop thru these items, and It should be a for-loop because the index is important.
My question is, is there a difference in performance between the following 2 options?
1.
for (int i = 0; i < items.Count(); i++)
{
//Do something
}
var itemsLength = items.Count();
for (int i = 0; i < itemsLength; i++)
{
//Do something
}
In other words, does the method items.Count() run again and again on each iteration in option 1?
Calling Count() on an enumerable source will exhaust the enumerable until all elements are enumerated. Calling Count() on an enumerable in the condition-block in a for loop will therefore exhaust the enumerable at every iteration. For example, calling
var numbers = VerboseRange(1, 5);
for (var index = 0; index < numbers.Count(); index++)
{
Console.WriteLine($"For-loop is at index {index}...");
}
IEnumerable<int> VerboseRange(int start, int count)
{
foreach (var number in Enumerable.Range(start, count))
{
Console.WriteLine($"Yielded number {number}.");
yield return number;
}
}
will output
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 0...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 1...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 2...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 3...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
For-loop is at index 4...
Yielded number 1.
Yielded number 2.
Yielded number 3.
Yielded number 4.
Yielded number 5.
Therefore, counting before is better.
However, I would recommend you to use a counter and a foreach-loop
var count = 0;
foreach (var item in items)
{
// do something
count++;
}
In C# 7.0, you can finally do
foreach (var (item, index) in items.WithIndex())
{
// do something
}
Usually when it comes to these type of performance questions, it's just easier to test it. Using BenchmarkDotnet :
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|----------:|----------:|------:|--------:|----------:|
| Option2 | 49.40 ns | 0.700 ns | 0.654 ns | 1.00 | 0.00 | - |
| Option1 | 955.00 ns | 16.993 ns | 15.064 ns | 19.30 | 0.34 | - |
Option1 is 20x slower. Here the IEnumerable was generated using Enumerable.Range(0,100)
I then also tested to see what would happen if your actual type was a List and whether the Count() is optimized to just return the Count property. These were the results:
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated |
|-------- |----------:|---------:|---------:|------:|--------:|----------:|
| Option2 | 40.53 ns | 0.246 ns | 0.192 ns | 1.00 | 0.00 | - |
| Option1 | 452.07 ns | 4.906 ns | 4.097 ns | 11.14 | 0.12 | - |
This time option1 is 11x slower, seems like it's only due to not having to generate the full 100 items enumerable.
Test cases used:
[Benchmark]
public static int Option1()
{
var x = 0;
for (int i = 0; i < Data.Count(); i++)
{
x++;
}
return x;
}
[Benchmark(Baseline = true)]
public static int Option2()
{
var itemsLength = Data.Count();
var x = 0;
for (int i = 0; i < itemsLength; i++)
{
x++;
}
return x;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With