C# struct x class benchmark
- Gerson Reis
- 28 de jan. de 2021
- 4 min de leitura
Atualizado: 6 de jun. de 2022

Olá, escrevi este post de forma bem breve para relatar um desafio que como um desenvolvedor tenho no dia a dia. Vou levar em consideração que você tenha alguma intimidade com c# e gerenciamento de memória pois não me aprofundei em detalhes, vou focar mais na dica simples e rápida e junto a ela tentar de alguma forma representar o ganho com alguns códigos e imagens. Bom, recentemente tive uma situação no o time em que trabalho, um integrante precisava reduzir o processamento de uma azure function, resumidamente o que ela faz é: obtém alguns dados no cosmosDB e em alguns casos faz uma leitura/análise e retorna os mesmos. O que acontece é que precisamos baixar o custo desse processamento, apesar de a tarefa não estar atribuída a mim tentei buscar alternativas e sugerir a pessoa que estava com a missão.
Curiosamente acreditei que talvez fazer o uso da struct que muitas vezes é esquecida pelos devs(inclusive por mim) poderia nos ajudar em alguma coisa, e sim ela ajuda! Fiz um teste usando uma ferramenta de benchmark para ver a diferença e deixar o resultado palpável através de números.
Segue o código que escrevi para testar e na sequência os resultados:
public struct CarroStruct
{
public CarroStruct(string name, int value)
{
Name = name;
this.value = value;
}
public string Name { get; set; }
public int value { get; set; }
}
public class CarroClass
{
public CarroClass(string name, int value)
{
Name = name;
this.value = value;
}
public string Name { get; set; }
public int value { get; set; }
}
[MemoryDiagnoser]
public class DataTableMonitor
{
[Benchmark]
public bool TestStruct()
{
CarroStruct[] cars = new CarroStruct[10]
{
new CarroStruct("red", 2020),
new CarroStruct("blue", 2015),
new CarroStruct("orange", 2014),
new CarroStruct("black", 2015),
new CarroStruct("orange", 2019),
new CarroStruct("red", 2010),
new CarroStruct("blue", 2019),
new CarroStruct("blue", 2016),
new CarroStruct("red", 2012),
new CarroStruct("black", 2017)
};
foreach (var item in cars) { }
return true;
}
[Benchmark]
public bool TestClass()
{
CarroClass[] cars = new CarroClass[10]
{
new CarroClass("red", 2020),
new CarroClass("blue", 2015),
new CarroClass("orange", 2014),
new CarroClass("black", 2015),
new CarroClass("orange", 2019),
new CarroClass("red", 2010),
new CarroClass("blue", 2019),
new CarroClass("blue", 2016),
new CarroClass("red", 2012),
new CarroClass("black", 2017)
};
foreach (var item in cars) { }
return true;
}
Acima temos uma strcut, uma classe e na sequencia dois testes para rodar com benchmark, cada um teste cria uma coleção de struct e lê a mesma com um foreach e o outro testa faz a mesma coisa mas no lugar da struct é uma classe. Simples e direto.
Logo na sequencia já coloquei o teste pra funcionar e obtive os seguintes resultados:

Se você não soube interpretar os dados lhe recomendo ler este artigo aqui antes, pois nele fiz alguns exemplos e mostrei uma forma simples de ver métricas básicas com o BenchmarkDotNet.
Baixamos para menos de a metade o consumo de recursos, agora apenas 184 dos 424 que utilizavamos.
Incrível não? Algo realmente muito simples e um recurso que está aí para ser utilizado, lembre-se de não exagerar e querer usar em tudo porque ela tem seus contras também.
Seguindo...
Quando usamos coleções de estruturas de dados os recursos ficam um pouco mais reduzidos quando comparados a uma coleção de objetos.
O que muda?
O que muda é que quando temos uma coleção de objetos, essa lista/array tem apenas a referência do objeto na memória e isso nos permite ler um a um e alterar os valores do mesmo caso esse seja o objetivo. Temos liberdade de alterar os valores do item enquanto interagimos com a lista e na memoria as coisas se organizam mais ou menos assim:

Já quando temos uma coleção de estruturas de dados, a estrutura em si está dentro da coleção e é tudo uma coisa só dentro do heap. O formato que o c# trabalhar não nos permite mudar o valor de uma coleção quando a mesma está alocada para leitura, ou seja: Se você estiver dentro de um simples foreach na sua coleção de struct não conseguirás mudar o valor de nenhuma delas pois o foreach está utilizando a estrutura como um todo.
então uma coleção de struct fica algo parecido com isto:

Então tentar alterar o dados de uma struct que pertence a uma coleção no mesmo momento em que você está percorrendo a mesma, seria igual quando você está fazendo um foreach ou um for em um ICollection<T> e dentro deste "for" em determinada condição você tenta fazer um “collection.Remove(item)”. A tecnologia não vai permitir isto.
Exemplo da situação:
ICollection<CarroClass> cars = new List<CarroClass>();
cars.Add(new CarroClass("red", 2020));
foreach (var item in cars)
{
cars.Remove(item);
}
Podes escrever o mesmo e executar para entenderes melhor o que acontece. Resumidamente: Para algumas situações use struct, mas para utiliza-las entenda como ela se comporta na memoria, não só ela mas como todos os outros recursos é importante entender como se comportam e quais "efeitos colaterais" cada um cria no ambiente da sua aplicação, é isso que vai lhe diferenciar dos outros 95% dos desenvolvedores.
Espero de alguma forma ter agregado algo para seu dia, se você tem algum ponto a melhorar neste post por favor me avise, estamos aqui para evoluir juntos o que está escrito aqui é o que sei até o momento mas acredito que sempre tenho a aprender muito e mudar minha opinião sobre muitas coisas, caso achares que podes me ajudar ficarei grato!
Keep learning and até a proxima.
Imagens retiradas deste post.
Comments