Performance of the Loops … in C#/.net8
Introduction
Several years ago, specifically in 2017, I have written an article about the different performances of “loops” available in C#/.Net.
Fast forward to 2024, it is interesting to see how accurate and real these measurements are with the new .net 8!
We will use roughly the same scenario: given a large array or list of integers, the idea is to loop through these sequences and verify how much better or worse the same type of loops will perform.
Like the test 7 years ago, unsurprisingly the “while” and “for” loops have similar performance.
The big enhancement is the “foreach” that got clearly a big improvement, sometimes outperforming the standard “for” loop.
So now you can use “foreach” without having a second thought on the performance hit. However this is not a key that opens all doors, it needs to be used wisely, where you avoid boxing/unboxing, conversions and whatever adds more delay. This is important because with “foreach” it is easily to fall in such a trap.
As in the first article, those that lag behind are still “foreach with enumerable conversion” and the Array.ForEach. It comes without surprises since they add more layers of indirection such as virtual/lambda function calls.
Note that the performance is similar when using array/list of objects instead of “int”.
Project
The implementation is straight forward, where each type of loops is tested in its own method. While the use of array or list<int> is implemented in different class.
Implementing object[] or list<object> is literally the same, so the code will not be included.
using System.Diagnostics;
namespace LoopPerformance
{
public class LoopPerformanceArray
{
const int MAX_LEN = 100_000_000;
static int[] intValues = new int[MAX_LEN];
public static void Run()
{
FillArray();
Console.WriteLine($"Measure loop performance with array\n");
for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"#{i} ===================\n");
MeasureWhileLoop();
MeasurePlainForLoop();
MeasureForEachLoop();
MeasureForEachEnumerableLoop();
MeasureArrayForEachLoop();
Console.WriteLine();
}
}
private static void MeasureWhileLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
int i = 0;
while (i < intValues.Length)
{
val = intValues[i] * 10;
i++;
}
stopwatch.Stop();
Console.WriteLine($"WhileLoop: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasurePlainForLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
for(int i = 0; i < intValues.Length; i++)
{
val = intValues[i] * 10;
}
stopwatch.Stop();
Console.WriteLine($"PlainForLoop: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasureForEachLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
foreach (var v in intValues)
{
val = v * 10;
}
stopwatch.Stop();
Console.WriteLine($"ForEachLoop: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasureForEachEnumerableLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
var enumArray = (IEnumerable<int>)intValues;
foreach (var v in enumArray)
{
val = v * 10;
}
stopwatch.Stop();
Console.WriteLine($"ForEachEnumerable: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasureArrayForEachLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
Array.ForEach<int>(intValues, (v) => val = v * 10);
stopwatch.Stop();
Console.WriteLine($"Linq Array.ForEach: {stopwatch.ElapsedMilliseconds}");
}
private static void FillArray()
{
Random rnd = new Random();
for (int i = 0; i < intValues.Length; i++)
{
intValues[i] = rnd.Next();
}
}
}
}
The LoopPerformanceList class uses the same types of loops but this time the data structure is a List<int> instead of array of integers (int[])
using System.Diagnostics;
namespace LoopPerformance
{
public class LoopPerformanceList
{
const int MAX_LEN = 100_000_000;
static List<int> intValues = new List<int>(MAX_LEN);
public static void Run()
{
FillArray();
Console.WriteLine($"Measure loop performance with list\n");
for (int i = 1; i <= 5; i++)
{
Console.WriteLine($"#{i} ===================\n");
MeasureWhileLoop();
MeasurePlainForLoop();
MeasureForEachLoop();
MeasureForEachEnumerableLoop();
MeasureArrayForEachLoop();
Console.WriteLine();
}
}
private static void MeasureWhileLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
int i = 0;
while (i < intValues.Count)
{
val = intValues[i] * 10;
i++;
}
stopwatch.Stop();
Console.WriteLine($"WhileLoop: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasurePlainForLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
for(int i = 0; i < intValues.Count; i++)
{
val = intValues[i] * 10;
}
stopwatch.Stop();
Console.WriteLine($"PlainForLoop: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasureForEachLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
foreach (var v in intValues)
{
val = v * 10;
}
stopwatch.Stop();
Console.WriteLine($"ForEachLoop: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasureForEachEnumerableLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
var enumArray = (IEnumerable<int>)intValues;
foreach (var v in enumArray)
{
val = v * 10;
}
stopwatch.Stop();
Console.WriteLine($"ForEachEnumerable: {stopwatch.ElapsedMilliseconds}");
}
private static void MeasureArrayForEachLoop()
{
Stopwatch stopwatch = Stopwatch.StartNew();
int val;
intValues.ForEach((v) => val = v * 10);
stopwatch.Stop();
Console.WriteLine($"Linq Array.ForEach: {stopwatch.ElapsedMilliseconds}");
}
private static void FillArray()
{
Random rnd = new Random();
for (int i = 0; i < MAX_LEN; i++)
{
intValues.Add(rnd.Next());
}
}
}
}
Outcome
The takeaway is summarized in the table below. If you are working to gain performance, use “while/for” loops. Also use the “foreach” with some extra care to avoid inferring implicit conversions.
Conclusion
High performance matters only when there is a benefit to be extracted from. For ordinary application this might not be the case, no user will judge you if your code is one millisecond faster. However if this millisecond is what makes you win or lose interesting deals on an electronically trading market, then it makes sense to invest in it.




