Performance de iterações :: while, for, foreach, linq
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:
Segundo teste:
Terceiro:
Quarto:
Último:
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
Legal Gustavo, realmente um resultado interessante.
Abraços.
Just passing by.Btw, you website have great content!
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!
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