2

Consider this:

Requisite:

//The alphabet from a-z
List<char> letterRange = Enumerable.Range('a', 'z' - 'a' + 1)
.Select(i => (Char)i).ToList(); //97 - 122 + 1 = 26 letters/iterations

Standard foreach:

foreach (var range in letterRange)
{
    Console.Write(range + ",");
}
Console.Write("\n");

Inbuilt foreach:

letterRange.ForEach(range => Console.Write(range + ",")); //delegate(char range) works as well
Console.Write("\n");

I have tried timing them against each other and the inbuilt foreach is up to 2 times faster, which seems like a lot.

I have googled around, but I can not seem to find any answers.

Also, regarding: In .NET, which loop runs faster, 'for' or 'foreach'?

for (int i = 0; i < letterRange.Count; i++)
{
    Console.Write(letterRange[i] + ",");
}
Console.Write("\n");

Doesn't act execute faster than standard foreach as far as I can tell.

Community
  • 1
  • 1
CasperT
  • 3,425
  • 11
  • 41
  • 56

3 Answers3

17

I think your benchmark is flawed. Console.Write is an I/O bound task and it's the most time consuming part of your benchmark. This is a micro-benchmark and should be done very carefully for accurate results.

Here is a benchmark: http://diditwith.net/PermaLink,guid,506c0888-8c5f-40e5-9d39-a09e2ebf3a55.aspx (It looks good but I haven't validated it myself). The link appears to be broken as of 8/14/2015

Luis Perez
  • 27,650
  • 10
  • 79
  • 80
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • I believe you are right. Are you able to provide some proper benchmark results? – CasperT Apr 13 '09 at 14:43
  • Instead, create a stringbuilder with enough initial capacity to hold the entire result and append each string to that. Then output everything all once, and after you've stopped your timer. – Joel Coehoorn Apr 13 '09 at 14:46
  • ah, I didn't notice that article features Lists.ForEach benchmarks as well. Thanks then – CasperT Apr 13 '09 at 14:49
  • 1
    Microsoft disagrees according to one of its articles: https://learn.microsoft.com/en-us/visualstudio/ide/reference/convert-foreach-linq?view=vs-2019 – ATL_DEV Oct 04 '21 at 02:03
  • @ATL_DEV Could you please explain what exactly in that page you are referring to that suggests "Microsoft disagrees" with the answer? – Mehrdad Afshari Oct 06 '21 at 03:29
  • "Note LINQ syntax is typically less efficient than a foreach loop. It's good to be aware of any performance tradeoff that might occur when you use LINQ to improve the readability of your code." – ATL_DEV Oct 06 '21 at 03:34
  • @ATL_DEV I don't think I've contradicted that in this 12 years old answer, in which it seems I was merely suggesting that the questioner's benchmark is totally unreliable, not commenting on which way is more performant (and referring to a link whose benchmark was more solid, which is dead today, evidently ;)). – Mehrdad Afshari Oct 07 '21 at 07:03
  • @ATL_DEV `List.ForEach` is an [actual method](https://learn.microsoft.com/dotnet/api/system.collections.generic.list-1.foreach) of `List`, not a LINQ extension. – LWChris Aug 18 '23 at 06:43
11

When you enter a foreach loop, you enumerate over each item. That enumeration causes two method calls per iteration: one to IEnumerator<T>.MoveNext(), and another to IEnumerator<T>.Current. That's two call IL instructions.

List<T>.ForEach is faster because it has only one method call per iteration -- whatever your supplied Action<T> delegate is. That's one callvirt IL instruction. This is significantly faster than two call instructions.

As others pointed out, IO-bound instructions like Console.WriteLine() will pollute your benchmark. Do something that can be confined entirely to memory, like adding elements of a sequence together.

John Feminella
  • 303,634
  • 46
  • 339
  • 357
  • 1
    Microsoft disagrees, https://learn.microsoft.com/en-us/visualstudio/ide/reference/convert-foreach-linq?view=vs-2019 – ATL_DEV Oct 04 '21 at 02:02
2

This is because the foreach method is not using an enumerator. Enumerators (foreach) tend to be slower then a basic for loop:

Here's the code for the ForEach method:

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

While I would expect there to be a difference, I'm a little surprised it's as large as you indicated. Using the enumerator approach you are taking an extra object creation, and then extra steps are taken to ensure the enumerator is not invalidated (the collection is modified). Your also going through an extra function call Current() to get the member. All this adds time.

JoshBerke
  • 66,142
  • 25
  • 126
  • 164