C# — Value Types 60% mais rápido que References Types
Na maioria das vezes que queremos representar algo do mundo real no código, partimos para uma abordagem utilizando classes. Não é comum encontrarmos essas representações como estruturas, mesmo quando são objetos anêmicos, sem nenhum tipo de comportamento. Mas será que vale a pena investirmos um pouco mais de tempo para definir qual a maneira mais eficiente de escrevermos nosso código, ao invés de seguirmos o padrão? Pois digo que sim, vale bastante a pena. Vamos ver o cenário abaixo.
Temos a necessidade de implementar o preenchimento de uma lista com alguns objetos, e depois realizar algumas consultas nessa lista. Os objetos são pequenos e sem comportamento algum. Este é um cenário bem comum de problemas que encontramos por aí. Vamos ao código para demonstrar os resultados e depois esclarecemos o porque das coisas.
Esse artigo será dividido em duas partes, a primeira com as implementações e os resultados obtidos, e posteriormente, num segundo artigo explicarei o motivo da diferença de comportamento entre as implementações.
Implementação com class
Vamos pensar que o objeto do cenário acima seja a classe Dimensao representada abaixo:
Vamos popular uma lista com alguns objetos e realizar a busca de um item que sabemos que não existe na lista e vamos mensurar essa execução.
Utilizando o pacote BenchmarkDotNet para realizar a medição, temos os resultados abaixo:
Implementação com struct
Agora ao invés de utilizarmos uma classe vamos utilizar uma estrutura, struct.
Vamos medir a inserção dos itens na lista e a consulta.
Resultado:
Podemos observar que já tivemos um ganho significativo. Saímos de um tempo de execução de 427 milissegundos, para 161 milissegundos. Mas podemos ver que o consumo de memória aumentou um pouco, de 155 MB para 187 MB. Isso se deve ao fato das struct serem armazenadas na Stack, mas o método Equals recebe um object, portanto temos o famoso boxing/unboxing para a conversão de um objeto valor para um objeto referência. Mas podemos contornar esse problema utilizando a interface IEquatable<T>.
Implementação com estrutura e IEquatable<T>
Implementando a interface IEquatable<T> a estrutura ficaria assim:
Na inserção e busca não teríamos diferença:
Medindo novamente, temos ganhos no tempo de execução e também na alocação de memória.
Observamos uma melhora significativa de tempo de execução, de 427 milissegundos para 136 milissegundos, e na alocação de memória, de 155MB para 96 MB.
Portanto, vale muito a pena analisarmos a melhor maneira de representarmos nossos objetos. Nem sempre a utilização do struct será a melhor opção, devemos sempre analisar os cenários e realizar as medições para essa conclusão.
Para entendermos melhor o motivo da diferença de desempenho e como podemos definir entre class ou struct, no próximo artigo vou abordar como o .net realiza o gerenciamento de memória pois isso é o core para entendermos a diferença de comportamento entre as implementações acima.
O código completo destes exemplos estão disponíveis em meu GitHub.
Muito obrigado e até mais ;)