Gustavo Vedotti

Performance de iterações :: while, for, foreach, linq

Posted in .net, Linq, performance by agvedotti on 02/10/2009

Quem será o mais rápido? Para descobrir isso criei um projeto de teste da seguinte forma:

Uma classe com o método main() que instancia a classe de teste e imprime os resultados:

1: class Program
2: {
3:     static void Main(string[] args)
4:     {
5:         TestePerf teste = new TestePerf();
6:
7:         Produto f = teste.listaProdutos[teste.listaProdutos.Count / 2];
8:
9:         List<Resultado> resultados = new List<Resultado>();
10:
11:         resultados.Add(teste.AcharLinq(f.idProduto));
12:         resultados.Add(teste.AcharForeach(f.idProduto));
13:         resultados.Add(teste.AcharFor(f.idProduto));
14:         resultados.Add(teste.AcharWhile(f.idProduto));
15:
16:         resultados.Sort(delegate(Resultado r1, Resultado r2)
17:         {
18:             if (Convert.ToInt32(r1.ticks) > Convert.ToInt32(r2.ticks))
19:                 return 0;
20:             else
21:                 return 1;
22:         });
23:
24:         foreach (Resultado r in resultados)
25:         {
26:             Console.WriteLine(r.iterador +
27:                     "\tTicks:" + r.ticks +
28:                     "\tMilisegundos: " + r.miliseg);
29:         }
30:
31:         Console.Read();
32:     }
33:}

Uma classe que será o objeto da coleção percorrida:

1: class Produto
2: {
3:     public Produto()
4:     {
5:         this.idProduto = Guid.NewGuid().ToString();
6:     }
7:
8:     public string idProduto;
9: }

Uma classe para retornar os resultados:

1: class Resultado
2: {
3:     public string iterador;
4:     public string ticks;
5:     public string miliseg;
6:
7:     public Resultado(string iterador, string ticks, string miliseg)
8:     {
9:         this.iterador = iterador;
10:         this.ticks = ticks;
11:         this.miliseg = miliseg;
12:     }
13: }

E uma classe que é responsável pela execução do teste:

1: class TestePerf
2: {
3:     public List<Produto> listaProdutos;
4:     private const int QUANTIDADE = 500000;
5:
6:     public TestePerf()
7:     {
8:         listaProdutos = new List<Produto>();
9:
10:         this.PreencherLista();
11:     }
12:
13:     private void PreencherLista()
14:     {
15:         for (int i = 0; i < QUANTIDADE; i++)
16:             listaProdutos.Add(new Produto());
17:     }
18:
19:     public Resultado AcharLinq(string guid)
20:     {
21:         Stopwatch marcador = new Stopwatch();
22:
23:         marcador.Start();
24:
25:         Produto g = (from f in listaProdutos
26:                      where f.idProduto == guid
27:                      select f).FirstOrDefault();
28:
29:         marcador.Stop();
30:         return new Resultado(
31:             "Linq", marcador.ElapsedTicks.ToString(),
32:             marcador.ElapsedMilliseconds.ToString());
33:     }
34:
35:     public Resultado AcharFor(string guid)
36:     {
37:         Stopwatch marcador = new Stopwatch();
38:         marcador.Start();
39:         Resultado result = null;
40:
41:         for (int i = 0; i < listaProdutos.Count – 1; i++)
42:         {
43:             if (listaProdutos[i].idProduto == guid)
44:             {
45:                 marcador.Stop();
46:                 result = new Resultado(
47:                     "For", marcador.ElapsedTicks.ToString(),
48:                     marcador.ElapsedMilliseconds.ToString());
49:                 break;
50:             }
51:         }
52:
53:         return result;
54:     }
55:
56:     public Resultado AcharWhile(string guid)
57:     {
58:         Stopwatch marcador = new Stopwatch();
59:         marcador.Start();
60:         int cont = 0;
61:         Resultado result = null;
62:
63:         // Não façam isso em casa!
64:         while (true)
65:         {
66:             if (listaProdutos[cont++].idProduto == guid)
67:             {
68:                 marcador.Stop();
69:                 result = new Resultado(
70:                     "While", marcador.ElapsedTicks.ToString(),
71:                     marcador.ElapsedMilliseconds.ToString());
72:                 break;
73:             }
74:         }
75:
76:         return result;
77:     }
78:
79:     public Resultado AcharForeach(string guid)
80:     {
81:         Stopwatch marcador = new Stopwatch();
82:         marcador.Start();
83:         Resultado result = null;
84:
85:         foreach (Produto f in listaProdutos)
86:         {
87:             if (f.idProduto == guid)
88:             {
89:                 marcador.Stop();
90:                 result = new Resultado(
91:                     "ForEa", marcador.ElapsedTicks.ToString(),
92:                     marcador.ElapsedMilliseconds.ToString());
93:                 break;
94:             }
95:         }
96:
97:         return result;
98:     }
99: }

Esta última classe possui quatro métodos públicos que recebem um Guid e o pesquisam em uma coleção de ‘produtos’. O tamanho desta coleção é determinada pela variável constante QUANTIDADE, que no teste está com o valor de quinhentos mil.

Um Stopwatch foi usado para marcar o tempo, ele é iniciado quando a iteração começa, parado quando o Guid é encontrado e uma classe de resultado é instanciada com as informações da iteração e retornada ao main().

O teste foi executado cinco vezes e foi feita uma média nos resultados. Quem será que ganhou? Senhores, façam suas apostas!

Primeiro teste:
image

Segundo teste:
image

Terceiro:
image

Quarto:
image

Último:
image

E o vencedor é…: While! Tenho que assumir que é um dos iteradores que menos escrevo, normalmente utilizo o foreach e o linq para situações que ficariam muito complexas e/ou ‘feias’ em outros iteradores.

A idéia do post é apenas demonstrar a diferença entre cada um e não pregar qual deve ser utilizado, pois depende muito de cada situaçao e preferência do desenvolvedor. Só fica a dica para optarmos para o mais performático quando não houver diferença na utilização entre eles.

Gustavo Vedotti

4 Responses

Subscribe to comments with RSS.

  1. André Nobre said, on 02/11/2009 at 12:24 AM

    Legal Gustavo, realmente um resultado interessante.

    Abraços.

  2. Mike said, on 03/02/2009 at 2:36 PM

    Just passing by.Btw, you website have great content!

  3. Diego Frata said, on 03/06/2009 at 1:04 PM

    Gus,

    Interessante o teste, porém eu vejo alguns problemas:

    1) Você não informa a versão da framework, o que é muito importante visto que da versão 1.1 para a 3.5 a otimização que o compilador faz pode alterar o resultado dos testes.

    2) O teste está levando em consideração o tempo para achar um registro e não a performance das iterações. E se não existisse um registro para ser encontrado? O while ficaria em loop infinito.

    3) O while não tem a custo de comparar um Int32 a cada iteração já que você definiu uma condição constante para ele.

    Abraços!

    • agvedotti said, on 02/25/2010 at 2:22 PM

      Demorei mas respondi! =)
      1) A versão do framework tinha que ser a 3.5! Pois usei Linq.
      2) O teste foi criado com base que sempre existirá o registro a ser encontrado.
      3) Você está correto, nesse ponto o while ganha dos demais por não precisar fazer uma coração em cada iteração, deixando o resultado duvidoso.

      []s


Leave a reply to agvedotti Cancel reply