Written by 16:40 ASP .NET CORE, Languages & Coding, Performance Tuning • 2 Comments

Foreach or For – That is the Question

The discussion about the preference difference between FOREACH and FOR is not new. We all know that FOREACH is slower, but not all know why.

When I started learning .NET, one person told me that FOREACH is two times slower than FOR. He said this without any grounds. I took it for granted.

Eventually, I decided to explore FOREACH and FOR loop performance difference, and write this article to discuss nuances.
Let’s have a look at the following code:

foreach (var item in Enumerable.Range(0, 128))
{
  Console.WriteLine(item);
}

The FOREACH is a syntax sugar. In this particular case, the compiler transforms it into the following code:

IEnumerator<int> enumerator = Enumerable.Range(0, 128).GetEnumerator();
try
 {
   while (enumerator.MoveNext())
   {
     int item = enumerator.Current;
     Console.WriteLine(item);
   }
 }
finally
 {
  if (enumerator != null)
  {
   enumerator.Dispose();
  }
}

Knowing this, we can assume the reason for why FOREACH is slower than FOR:

  • A new object is being created. It is called Creator.
  • The MoveNext method is called on each iteration.
  • Each iteration accesses the Current property.

That’s it! However, it is not all as easy as it sounds.

Fortunately (or unfortunately), C#/CLR may perform optimizations at run time. The pro is that the code works faster. The con – developers should be aware of these optimizations.

The array is a type deeply integrated into CLR, and CLR provides a number of optimizations for this type. The FOREACH loop is an iterable entity, which is a key aspect of the performance. Later in the article, we will discuss how to iterate through arrays and lists with help of the Array.ForEach static method and the List.ForEach method.

Test Methods

static double ArrayForWithoutOptimization(int[] array)
{
   int sum = 0;
   var watch = Stopwatch.StartNew();
   for (int i = 0; i < array.Length; i++)
     sum += array[i];
    watch.Stop();
    return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForWithOptimization(int[] array)
{
   int length = array.Length;
   int sum = 0;
   var watch = Stopwatch.StartNew();
    for (int i = 0; i < length; i++)
      sum += array[i];
    watch.Stop();
     return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForeach(int[] array)
{
  int sum = 0;
  var watch = Stopwatch.StartNew();
   foreach (var item in array)
    sum += item;
  watch.Stop();
  return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForEach(int[] array)
{
  int sum = 0;
  var watch = Stopwatch.StartNew();
  Array.ForEach(array, i => { sum += i; });
  watch.Stop();
  return watch.Elapsed.TotalMilliseconds;
}

Test conditions:

  • The “Optimize code” option is turned on.
  • The number of elements is equal to 100 000 000 (both in the array and list).
  • PC specification: Intel Core i-5 and 8 GB of RAM.

Arrays

Iterating Arrays

The diagram shows that FOR and FOREACH spend the same amount of time while iterating through arrays. And it is because CLR optimization converts FOREACH into FOR, and uses the length of the array as the maximum iteration boundary. It does not matter whether the array length is cached or not (when using FOR), the result is almost the same.

It may sound strange, but caching the array length may affect performance. While using array.Length as the iteration boundary, JIT tests the index to hit into the right border beyond the cycle. This check is performed only once.
It is very easy to destroy this optimization. The case when the variable is cached is hardly optimized.

Array.foreach demonstrated the worst results. Its implementation is quite simple:

public static void ForEach<T>(T[] array, Action<T> action)
 {
  for (int index = 0; index < array.Length; ++index)
    action(array[index]);
 }

Then why is it running so slow? It uses FOR under the hood. Well, the reason is in calling the ACTION delegate. In fact, a method is called on each iteration, which decreases performance. Moreover, the delegates are invoked not as fast as we would like.

Lists

Iterating Lists

The result is completely different. When iterating lists, FOR and FOREACH show different results. There is no optimization. FOR (with caching the length of the list) shows the best result, whereas FOREACH is more than 2 times slower. It is because it deals with MoveNext and Current under the hood. List.ForEach as well as Array.ForEach shows the worst result. Delegates are always called virtually. The implementation of this method looks like this:

public void ForEach(Action<T> action)
{
  int num = this._version;
   for (int index = 0; index < this._size && num == this._version; ++index)
     action(this._items[index]);
   if (num == this._version)
     return;
   ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}

Each iteration calls the Action delegate. It also checks whether the list is changed and if so, an exception is thrown.

List internally uses an array-based model and the ForEach method uses the array index to iterate through, which is significantly faster than using the indexer.

Arrays vs Lists

Specific numbers

  1. The FOR loop without length caching and FOREACH work slightly faster on arrays than FOR with length caching.
  2. Array.Foreach performance is approximately 6 times slower than FOR / FOREACH performance.
  3. The FOR loop without length caching works 3 times slower on lists, comparing to arrays.
  4. The FOR loop with length caching works 2 times slower on lists, comparing to arrays.
  5. The FOREACH loop works 6 times slower on lists, comparing to arrays.

Here is a leader board for lists:

Lists Range

And for arrays:

Arrays Range

Conclusion

I really enjoyed this investigation, especially the writing process, and I hope you have enjoyed it as well. As it turned out, FOREACH is faster on arrays than FOR with length chasing. On list structures, FOREACH is slower than FOR.

The code looks better when using FOREACH, and modern processors allow using it. However, if you need to highly optimize your codebase, it is better to use FOR.

What do you think, which loop runs faster, FOR or FOREACH?

Tags: , , Last modified: September 23, 2021
Close