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?
CodePudding user response:
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;
}
CodePudding user response:
Calling Count() in a for loop will call it 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
}
CodePudding user response:
This link should give you more information but yes for is about two times cheaper than foreach.
