Which loop is faster for or foreach in C#?

Asked

Viewed 2,134 times

7

I’ve read articles from some programming languages that loop for is faster than the foreach, and wanted to know if C# has performance differences?

2 answers

12


When it comes to performance, the for is faster, but the foreach is more readable.

inserir a descrição da imagem aqui

I did tests iterating 10 thousand times and another thousand times with different data types, just to get an idea. In all cases For won.

I used the following code

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace BanchMark.ForAndForeach
{
    class Program
    {
       private static List<int> lista; 
       private static int[] array;
       private static HashSet<int> hashSet;

       static void Main(string[] args)
       {
           int qtdRegistros = 99999;
           int iterar= 10000;
           CriarCasos(qtdRegistros);
           ForeachVsFor(iterar);
           qtdRegistros = 99999;
           iterar = 1000;
           CriarCasos(qtdRegistros);
           ForeachVsFor(iterar);
           Console.ReadLine();
       }

    private static void ForeachVsFor(int repetir)
    {
        int test = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < repetir; i++)
        {
            foreach (var o in array)
            {
                test = i + i;
            }
        }
        watch.Stop();
        Console.WriteLine($"Foreach/Array {repetir} vezes: {watch.ElapsedMilliseconds}ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < repetir ;i++)
        {
            for (int j = 0; j < array.Length;j++)
            {
                test = i + i;
            }
        }
        watch.Stop();
        Console.WriteLine($"For/Array {repetir} vezes: {watch.ElapsedMilliseconds}ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < repetir; i++)
        {
            foreach (var o in lista)
            {
                test = i + i;
            }
        }
        watch.Stop();
        Console.WriteLine($"Foreach/Lista {repetir} vezes: {watch.ElapsedMilliseconds}ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < repetir; i++)
        {
            for (int j = 0; j < lista.Count; j++)
            {
                test = i + i;
            }
        }
        watch.Stop();
        Console.WriteLine($"For/Lista {repetir} vezes: {watch.ElapsedMilliseconds}ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < repetir; i++)
        {
            foreach (var o in hashSet)
            {
                test = i + i;
            }
        }
        watch.Stop();
        Console.WriteLine($"Foreach/HashSet {repetir} vezes: {watch.ElapsedMilliseconds}ms");

        watch = Stopwatch.StartNew();
        for (int i = 0; i < repetir; i++)
        {
            for (int j = 0; j < hashSet.Count; j++)
            {
                test = i + i;
            }
        }
        watch.Stop();
        Console.WriteLine($"For/HashSet {repetir} vezes: {watch.ElapsedMilliseconds}ms");

    }

    private static void CriarCasos(int tamanho)
    {
        Console.WriteLine("***************************************");

        lista = new List<int>(tamanho);
        Random random = new Random();
        Parallel.For(1, tamanho, i =>
        {
            lista.Add(random.Next(tamanho));
        });
        array = lista.ToArray();
        hashSet = new HashSet<int>(lista);
      }
   }
}
  • These tests do not fully reflect reality. Of course, without seeing the code used in the tests, can not assume many things, but take 4.262ms to run a foreach certainly has something quite time consuming happening within the loop, which prevents the more accurate measurement of the overhead and one or another method. In . NET we must also consider JIT: we must ensure that the code paths are already compiled and optimized by JIT. And finally, Hashset has no option to use with For except the LINQ Elementat and is extremely slow! I’ll post my tests next.

  • What code would be used to test?

11

Before actually answering I think it is very important to point out that for 99.99% of all applications developed in C# the differences in performance between for and foreach should not even be taken into consideration. It is a huge waste of time worry about this level of micro optimization before it really proves necessary. A premature optimization is the root of all evil in systems engineering, would our poet supreme Donald Knuth.

That said, answering this question is an interesting exercise and the end result may surprise some. However, first we have to stick to some inherent details. NET before we start testing the performance of the two constructs for iterations:

  1. . NET performs optimizations only in the RELEASE build
  2. . NET compiles at runtime (JIT)
  3. The amount of iterations should be large enough
  4. It is necessary to access the items in question in the internal code of the loop

Item 1 is extremely important. If the tests are run in DEBUG mode, the . NET will not apply compiler optimizations, and this can transform completely the results of the tests. In an article on replacing string whitespaces the test results turned upside down when run as RELEASE vs DEBUG.

Item 2 is important because it can negatively affect the first test, or the first runs of each test. The compile time of the code at runtime can be a considerable part of the measurement.

Item 3 should never be underestimated, as with few iterations you may be measuring the time of each instruction executed within the loop than the performance differences between each language construction.

Item 4 is self-explanatory, but it is important to note that if the items of the collections are not accessed within the loop, the test will not be fair between for and foreach because this always accesses the item in question.


Let’s go to the results then! And then to 1,000,000 (one million) iterations (the for of HashSet since the only way to pick up an item per index is through the extension method ElementAt LINQ and takes a long time, since sets are not indexed internally by a positional index):

For vs ForEach 1.000.000 de iterações

We can see that to scan an array there is almost no difference between foreach and for. This is because the . NET optimizes the foreach for arrays in such a way that it gets to be faster than a for, since in this case we need to access the item of the array by the index at the beginning of the loop and in the foreach It is already available directly in the construction of language. Most people will be surprised by this result, even more if it comes from other programming languages where the for is one of the fastest and almost unbeatable constructions to implement iterations.

In the case of item iteration of a list (a List<Data>) the for took 60% of the time foreach to perform the same operation. This particularly surprised me, as there is not much explanation for this time difference. I will analyze the IL ("machine" code) generated for each of the methods to try to understand where all this difference is coming from (and I will update the answer if necessary/interesting).

Still, this difference is negligible (for the vast majority of applications) if we consider that, in 1 million iterations, only 10ms were added to the runtime.

The HashSet, as stated earlier, cannot be tested with the for because for that amount of iterations it would take a long time (thing of several minutes for each test cycle).

The complete code of the test program follows:

class Program
{
    const int MAX_TEST_DATA_LENGTH = 1000000; //1000000;
    const int MAX_FOR_HASHSET = 10000;
    static int testDataLength = MAX_TEST_DATA_LENGTH;

    static void Main(string[] args) {
        if (args.Length > 0)
            testDataLength = Convert.ToInt32(args[0]);
        Console.WriteLine("For vs ForEach Peformance\r\n");
        Console.Write(" ■ Inicializando dados de teste com {0} registros", testDataLength);
        watch.Start();
        baseData = new Data[testDataLength];
        for (int i = 0; i < testDataLength; i++) {
            baseData[i] = new Data(i);
        }
        listData = new List<Data>(baseData);
        arrayData = listData.ToArray();
        hashsetData = new HashSet<Data>(baseData);
        DisplayElapsed();

        Restart(" ■ Forçando JIT (compilação em runtime)");
        ForArray(); ForEachArray(); ForList(); ForEachList(); ForHashset(); ForEachHashset();
        DisplayElapsed();

        for (int i = 1; i <= 5; i++) {

            Console.WriteLine("\r\n*** Ciclo {0} de teste:\r\n", i);

            // array
            Restart(" ■ For array");
            ForArray(testDataLength);
            DisplayElapsed();
            Restart(" ■ ForEach array");
            ForEachArray(testDataLength);
            DisplayElapsed();
            // List
            Restart(" ■ For List");
            ForList(testDataLength);
            DisplayElapsed();
            Restart(" ■ ForEach List");
            ForEachList(testDataLength);
            DisplayElapsed();
            // Hashset
            Restart(" ■ For HashSet");
            if (testDataLength > MAX_FOR_HASHSET) {
                Console.Write(" >>> inviável acima de {0} iterações", MAX_FOR_HASHSET);
            } else {
                ForHashset(testDataLength);
            }
            DisplayElapsed();
            Restart(" ■ ForEach HashSet");
            ForEachHashset(testDataLength);
            DisplayElapsed();

        }

        Console.WriteLine("\r\nTecle algo para encerrar");
        Console.ReadKey(true);
    }

    static Stopwatch watch = new Stopwatch();

    static Data[] baseData;
    static Data[] arrayData;
    static List<Data> listData;
    static HashSet<Data> hashsetData;

    static void Restart(string msg) {
        Console.Write(msg);
        watch.Restart();
    }

    static void DisplayElapsed() {
        var elapsed = watch.Elapsed;
        Console.WriteLine(" >>> duração: {0}ms", elapsed.TotalMilliseconds);
    }

    static void ForArray(int iterations = 10) {
        for (int i = 0; i < iterations; i++) {
            var item = arrayData[i];
            if (item.TheInt > MAX_TEST_DATA_LENGTH)
                throw new InvalidOperationException("Tamanho inválido de iterações");
        }
    }
    static void ForEachArray(int iterations = 10) {
        foreach(var item in arrayData) {
            if (item.TheInt > MAX_TEST_DATA_LENGTH)
                throw new InvalidOperationException("Tamanho inválido de iterações");
        }
    }
    static void ForList(int iterations = 10) {
        for (int i = 0; i < iterations; i++) {
            var item = listData[i];
            if (item.TheInt > MAX_TEST_DATA_LENGTH)
                throw new InvalidOperationException("Tamanho inválido de iterações");
        }
    }
    static void ForEachList(int iterations = 10) {
        foreach (var item in listData) {
            if (item.TheInt > MAX_TEST_DATA_LENGTH)
                throw new InvalidOperationException("Tamanho inválido de iterações");
        }
    }
    static void ForHashset(int iterations = 10) {
        for (int i = 0; i < iterations; i++) {
            var item = hashsetData.ElementAt(i);
            if (item.TheInt > MAX_TEST_DATA_LENGTH)
                throw new InvalidOperationException("Tamanho inválido de iterações");
        }
    }
    static void ForEachHashset(int iterations = 10) {
        foreach (var item in hashsetData) {
            if (item.TheInt > MAX_TEST_DATA_LENGTH)
                throw new InvalidOperationException("Tamanho inválido de iterações");
        }
    }

    class Data
    {
        public Data(int seed) {
            TheGuid = Guid.NewGuid();
            TheString = string.Format("{0}: {1}", seed, TheGuid);
            TheInt = seed;
            TheByteArray = new byte[1024];
        }
        public Guid TheGuid { get; set; }
        public string TheString { get; set; }
        public int TheInt { get; set; }
        public double TheDouble { get; set; }
        public byte[] TheByteArray { get; set; }
    }

}

Note that the test within each loop is performed only to ensure that the optimizer of the . NET does not resolve, simply, "disappear" with the body of the loop if it determines that nothing is being done inside it.

Completion? I believe it depends on each one. Particularly use foreach whenever I don’t need to know the current rate of an iteration within the loop code. If you see me doing things like var idx = myList.IndexOf(item) in the middle of a loop I transform it, without pity, into a for. Otherwise I forget until the for exists! :-)

  • Cool Loudenvier, was complete! Mine became too shallow to compare rs

  • 1

    @Lucaslopes actually if it wasn’t for your answer I would have just tested with array and not with List because I had the impression that the performance would be the same for both because of the optimizations of .NET. That’s why I like Statckoverflow more than other sites: each of the answers contributes something, Sometimes even the least voted have some detail that went unnoticed of the others! Hugging

  • Thanks guys, all the answers were very helpful.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.