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.
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: