C# - 5 Dicas para incrementar o desempenho do seu código


Em muitos cenários o desempenho deve ser alcançado a qualquer custo para viabilizar o sucesso de um projeto de software. Neste artigo, vamos apresentar 5 dicas para aumentar o desempenho do seu código C#.

Nos códigos de exemplos usados neste artigo eu vou utilizar o Visual Studio 2012 Express for desktop.

Abra o Visual Studio 2012 Express for desktop e crie um novo projeto (New Project) ;

Selecione o template Visual C# -> Console Application e informe o nome DicasDesempenhoCodigoCSharp e clique em OK;

Pronto. Temos uma solução onde eu irei criar os projetos para cada uma das dicas.

1- Defina o tipo de dados apropriado

Não se preocupar com a exatidão do tipo de dados a ser usado pode impactar o desempenho do seu código de forma negativa.

Renomeie o projeto DicasDesempenhoCodigoCSharp para 1TiposDadosApropriado.

Inclua o código baixo no método Main:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace DicasDesempenhoCodigoCSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Int32> li = new List<int>();
            Stopwatch sw = new Stopwatch();
            sw.Start();

            for (int i = 0; i < 10000; i++)
            {
                li.Add(i);
            }
            sw.Stop();
            Console.Write("Usando um Arraylist(Object)  -> tempo gasto : " + sw.ElapsedTicks + "\n");
            //-------------------------------------------------------------------------------------------------------
            sw.Reset();
            sw.Start();
            Int32[] a = new Int32[10000];
            for (int i = 0; i < 10000; i++)
            {
                a[i] = i;
            }
            sw.Stop();
            Console.Write("Usando Array de Inteiros (Integer Array)  -> tempo gasto : " + sw.ElapsedTicks);
            //---------------------------------------------------------------------------------------------------------
            Console.ReadLine();
        }
    }
}

Neste código estamos armazenando 1000 valores inteiros de duas formas distintas:

Para realizar a operação eu estou usando um laço for/next e atribuindo os valores a serem armazenados.

Qual das duas será executada mais rápidamente ?

Para medir o tempo gasto eu estou usando a classe StopWatch que fornece um conjunto de métodos e propriedades que você pode usar para medir precisamente um tempo decorrido. A classe StopWatch usa o namespace System.Diagnostics.

Executando o projeto veremos o resultado (os tempos podem variar):

Conclusão: Usar um array de inteiros ao invés de uma lista genérica é muito mais rápido para armazenar inteiros.

Mas Por que ?

Uma lista genérica armazena os dados no formato Object. Quando fazemos o armazenamento de inteiros eles são convertidos para um valor de referência para serem armazenados e isso tem um custo.

2- Use um laço for/next ao invés de foreach sempre que possível

Estando na solução criada no exemplo 1 clique no menu FILE -> Add -> New Project e informe o nome 2LacoForNext_Foreach;

A seguir clique com o botão direito do mouse sobre o projeto e selecione : Set as Startup Project

Inclua o código abaixo no método Main():

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace LacoForNext_Foreach
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Int32> Contador = new List<int>();
            List<Int32> lista1 = new List<Int32>();
            List<Int32> lista2 = new List<Int32>();

            for (int i = 0; i < 10000; i++)
            {
               Contador.Add(i);
            }
            Stopwatch sw = new Stopwatch();
            sw.Start();
            //---------------------------------------------------------
            for (int i = 0; i < Contador.Count; i++)
            {
               lista1.Add(i);
            }
            sw.Stop();
            //----------------------------------------------------------
            Console.Write("Laço For/Next :-> tempo gasto :  " + sw.ElapsedTicks + "\n");
            sw.Restart();
            foreach (int a in Contador)
            {
                lista2.Add(a);
            }
            sw.Stop();
            Console.Write("Laço Foreach :-> tempo gasto :  " + sw.ElapsedTicks);
            Console.ReadLine();
        }
    }
}

No código eu estou definindo três listas genéricas:

O laço foreach é usado para iterar sobre coleções enquanto o laço for/next pode ser usado realizar iterações mais genéricas.

Qual laço será executado mais rapidamente ?

Execute o projeto e veja você mesmo...

O laço for/next e mais rápido que o laço foreach.

O laço Foreach fornece um enumerator sobrecarregando o método IEnumerable.GetEnumerator. Isso faz com que ambos administrem a pilha e a função virtual em tipos simples causando um overhead. Mesmo usando coleções o laço for/next foi mais rápido

3 - Sempre utilize Stringbuilder para concatenar strings

Estando na solução criada no exemplo 1 clique no menu FILE -> Add -> New Project e informe o nome 3UsandoStringbuilder;

A seguir clique com o botão direito do mouse sobre o projeto e selecione : Set as Startup Project

Inclua o código a seguir no método Main():

using System;
using System.Diagnostics;
using System.Text;

namespace UsandoStringbuilder
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch st = new Stopwatch();

            string Letras = "AEIOU";
            st.Start();
            for (int i = 0; i < 500; i++)
            {
                Letras = Letras + "AEIOU";
            }
            st.Stop();
            //-----------------------------------------------------------------------------------
            Console.WriteLine("Usando String :-> tempo gasto : " + st.ElapsedTicks);
            st.Restart();
            StringBuilder sb = new StringBuilder("AEIOU");
            for (int i = 0; i < 500; i++)
            {
                  sb.Append("AEIOU");
            }
            st.Stop();
            Console.WriteLine("Usando Stringbuilder :-> tempo gasto : " + st.ElapsedTicks);
            Console.ReadLine();
        }
    }
}

O código acima efetua a concatenação de strings de duas formas:

Qual operação de concatenação de strings será executada mais rapidamente ?

Executando o projeto teremos:

A operação usando StringBuilder é MUITO mais rápida.

Dessa forma quando for realizar operações com strings use a classe StringBuilder que já aproveita o buffer que esta sendo usado para otimizar o desempenho.

O objeto String é imutável e cada vez que você usa um dos métodos da classe System.String você cria um novo objeto string na memória que requer uma nova alocação de espaço para o novo objeto.

Em situações onde você precisa realizar repetidas modificações em um string, a criação de um novo objeto String tem um custo elevado.

Ai entra o objeto StringBuilder, você já deve saber que o objeto StringBuilder é muito mais rápido para concatenar strings principalmente quando temos strings grandes.

Dessa forma se você tem que concatenar strings em um laço com muitas iterações a classe System.Text.StringBuilder é imbatível.

4 - Atribuindo dados a membros de uma classe

Estando na solução criada no exemplo 1 clique no menu FILE -> Add -> New Project e informe o nome 3UsandoStringbuilder;

A seguir clique com o botão direito do mouse sobre o projeto e selecione : Set as Startup Project

No menu PROJECT clique em Add Class e informe o nome Pessoa.cs e digite o código para definir a classe Pessoa contendo a propriedade Nome e membro Sobrenome ambos definidos como estáticos:

namespace AtribuicaoMembrosClasses
{
    public class Pessoa
    {
        public static string Nome { get; set; }
        public static string Sobrenome;
    }
}

A seguir digite o código a seguir no método Main():

using System;
using System.Diagnostics;

namespace AtribuicaoMembrosClasses
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch st = new Stopwatch();
            st.Start();
            for (int i = 0; i < 300; i++)
            {
                Pessoa.Nome = "Jose Carlos";
            }
            st.Stop();
            Console.WriteLine("usando  Property: -> tempo gasto : " + st.ElapsedTicks);
            st.Restart();
            for (int i = 0; i < 300; i++)
            {
                Pessoa.Sobrenome = "Macoratti";
            }
            st.Stop();
            Console.WriteLine("Usando Atribuição Direta (Membro) : -> tempo gasto : " + st.ElapsedTicks);
            Console.ReadLine(); 
        }
    }
}

O código acima estamos atribuindo valores à classe Pessoa:

Qual será o mais rápido ?

Executando o projeto veremos o resultado:

Como vemos a atribuição de valores usando uma propriedade é muito mais lenta. Se você preferir usar instâncias das classes e não definir os membros da classe como estáticos usar a propriedade ainda será mais lento.

5 - Utilize classes sealed(selada) quando puder

As classes que não precisam ser herdadas podem ser marcadas como sealed. As classes sealed removem as características das classes e permitem ao .NET Framework realizar várias otimizações em tempo de execução.

Quando aplicado a uma classe, o modificador sealed impede que outras classes herdem a partir desta classe. Ou seja uma classe sealed não pode ser herdada.

No exemplo a seguir, de classe B herda da classe A, mas nenhuma classe pode herdar da classe B.

Ao tentarmos declarar a classe C herdando da classe B teremos um erro em tempo de compilação indicando : Sealed.C': cannot derive from sealed type 'Sealed.B

Segue o projeto completo aqui: DicasDesempenhoCodigoCSharp.zip

João 7:16 Respondeu-lhes Jesus: A minha doutrina não é minha, mas daquele que me enviou.

João 7:17 Se alguém quiser fazer a vontade de Deus, há de saber se a doutrina é dele, ou se eu falo por mim mesmo.

Referências:


José Carlos Macoratti