Olá!
Este é mais um post da seção Desempenho e, desta vez, vamos tratar de uma ferramenta muito útil que nos auxilia a lidar com Span<T>
: O ZLinq.
Vamos lá!
Motivação
Como sabemos, não é possível escrever expressões Linq sobre spans e, por isso, é comum termos de iterar em coleções acessadas por esse tipo para realizamos operações.
O ZLinq ambiciona mudar esse comportamento!
Por meio de um método que tranforma seu Span<T>
em um ValueEnumerable
e permite que, em sua superfície, sejam executadas expressões como aquelas do Linq -- digo como aquelas do Linq e não as deste porque as expressões são todas implementadas pela própria biblioteca, que alega ter 99% das implementações do Linq (como os métodos Select
, Where
e Aggregate
por exemplo).
Aqui temos um exemplo do benchmarking realizado para fins de teste desta biblioteca:
[MemoryDiagnoser]
public class ZLinqBenchmark
{
[Benchmark]
public int Linq()
{
DummyAccount[] dummies = new DummyAccount[1_000];
for(int i = 0; i < dummies.Length; i++)
dummies[i] = new (Guid.NewGuid(), i * 100);
return
dummies.Where(d => d.Balance > 1_000)
.Select(d => d.AccountId)
.Count();
}
[Benchmark]
public int ZLinq()
{
Span<DummyAccount> dummies = stackalloc DummyAccount[1_000];
for (int i = 0; i < dummies.Length; i++)
dummies[i] = new(Guid.NewGuid(), i * 100m);
return
dummies.AsValueEnumerable()
.Where(d => d.Balance > 1_000)
.Select(d => d.AccountId)
.Count();
}
}
Como podemos observar, não há grandes diferenças entre os usos do Linq e Zlinq, na verdade há apenas duas: o método Linq
utiliza um array, enquanto o ZLinq
utiliza um Span<T>
e; o método ZLinq
lança mão do ValueEnumerable
.
Essas simples diferenças produziram o seguinte resultado no benchmarking:
Repare nas alocações: é uma economia de 32k por execução. Agora imagine isso no caminho crítico de uma aplicação, como oferece um desempenho superior por conta da necessidade reduzida de acionamentos do Garbage Collector. Impressionante!
Algo interessante aqui: em cenários onde os dois testes utilizem IEnumerable com tipos complexos, e o ZLinq não utilize um Span<T>
, alocações passam a ocorrer, como aquelas que ocorrem com o Linq.
Veja:
Além da alocação próxima, o tempo de execução foi um pouco maior. Ter esse conhecimento é interessante para evitar o uso indiscriminado da biblioteca. O ideal é utilizá-la quando há um cenário propício, no caso quando se usa Span<T>
.
Conclusão
ZLinq é uma ferramenta que entrega um desempenho ótimo ao lidarmos com Span<T>
tendo a necessidade de utilizar expressões Linq.
A biblioteca ainda está em começo de vida, conta com pouco mais de 20k downloads, mas é mantida pelo mesmo criador do MessagePack-CSharp sobre o qual já falamos aqui.
Vale a pena esperar por mais funcionalidades e melhorias com o tempo.
O código do benckmark está, como sempre, disponível no Github. Divirta-se e me diga o que achou.
Gostou do post? Me deixe saber pelos indicadores. Tem dúvidas ou sugestões? Deixe um comentário ou me procure em minhas redes.
Muito obrigado por ler até aqui, e até o próximo post.
Nice posting, I'd like to talk to you