Desempenho - Aprimorando Span com ZLinq
William Santos

William Santos @wsantosdev

About: Um Arquiteto de Software que se diverte pensando e escrevendo sobre princípios, práticas e padrões de design – e, eventualmente, código e desempenho de aplicações. E, também, Microsoft MVP.

Location:
Sao Paulo, Brazil
Joined:
Nov 1, 2019

Desempenho - Aprimorando Span com ZLinq

Publish Date: May 21
5 3

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

Linq vs ZLinq benchmark

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:

ZLinq over array benchmark

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.

Comments 3 total

Add comment