Breve introdução
System.Collections.Generic.List <T>é uma classe de coleção genérica em .NET, que pode armazenar qualquer tipo de dado devido à sua conveniência e API rica, amplamente usada em nosso dia a dia e pode ser considerada a classe de coleção mais utilizada.
Na escrita de código, muitas vezes precisamos iterar por uma coleção de Listas <T>para obter os elementos nela para algum processamento empresarial. Normalmente, não há muitos elementos dentro de um conjunto e é muito rápido de atravessar. Mas para alguns casos de processamento de big data, estatística, computação em tempo real, etc<T>., como navegar rapidamente pela coleção de listas de dezenas de milhares ou centenas de milhares de dados? É isso que preciso compartilhar com vocês hoje.
Modo de deslocamento
Vamos analisar o desempenho de diferentes métodos de travessia e construir o seguinte benchmark de desempenho, usando diferentes percursos de coleta de ordens de magnitude para ver o desempenho de diferentes métodos. O trecho do código é o seguinte:
Use a instrução foreach
Foreach é a forma mais comum de percorrermos coleções, é uma implementação syntax sugar do padrão iterator, e também é usada como referência para esse período.
Como a instrução foreach é um açúcar sintaxe, o compilador eventualmente chama GetEnumerator() e MoveNext() com um loop while para implementar a funcionalidade. O código compilado é o seguinte:
A implementação do método MoveNext() garantirá que não haja outras threads modificando a coleção na iteração e, se a modificação ocorrer, ela gerará uma exceção InvalidOperationException, e fará uma verificação de overflow para verificar se o índice atual é legítimo, além de precisar atribuir o elemento correspondente ao enumerador. Atributo atual,Então, na verdade, seu desempenho não é o melhor, o trecho de código é assim:
Vamos dar uma olhada em como ele se comporta em diferentes tamanhos de conjunto, e os resultados são os seguintes:
Pode-se ver que, no caso de tamanhos diferentes, a relação de crescimento linear do processo demorado é necessária, mesmo que ele percorra 100w de dados sem qualquer lógica de processamento, leva pelo menos 1s.
Use o método ForEach de Lista
Outra forma comum é usar o List<T>. ForEach(), que permite passar um <T>delegado de Ação, que chamará o delegado de Ação enquanto ele itera pelo <T>elemento.
É um <T>método de implementação interna do List, então pode acessar diretamente arrays privados e evitar verificações de overflow. Em teoria, deveria ser rápido; Mas no nosso cenário existe apenas um método vazio, que pode não se comportar bem com uma chamada totalmente inline para o método foreach. Abaixo está o código-fonte do método ForEach que mostra que ele não possui verificação de overflow, mas ainda mantém a verificação concorrente do número de versão.
Além disso, como é necessário passar um delegado para o método ForEach no código de chamada, ele verificará se o objeto delegado na classe de geração de fechamento está vazio toda vez, e se não, novo Action<T>(), como mostrado abaixo:
Vamos dar uma olhada em como ela se compara à palavra-chave foreach em termos de desempenho. A imagem a seguir mostra os resultados do benchmark:
Pelos resultados do teste, é 40% mais lento do que usar diretamente a palavra-chave foreach, parece que, se não for necessário, é uma escolha melhor usar foreach diretamente, então existe alguma forma mais rápida?
para percorrimento de laços
Voltando ao nosso método mais antigo, que é usar a palavra-chave for para percorrer a coleção. Deve ser o método de travessia com melhor desempenho no momento, porque não requer código redundante como os anteriores (embora o indexador também seja verificado para evitar transbordamentos), e obviamente não verifica o número da versão, então em um ambiente multithreaded a coleção é alterada, e não haverá exceção ao usar for. O código do teste é o seguinte:
Vamos ver como vai ficar.
Parece ser assim que esperamos.Usar o loop for diretamente é 60% mais rápido do que foreach, um conjunto que antes levava 1 segundo para percorrer, agora leva apenas 400 milissegundos. Então, existe uma maneira mais rápida?
Use CollectionsMarshal
Após o .NET5, a comunidade dotnet implementou a classe CollectionsMarshal para melhorar o desempenho das operações de coleta. Esta classe implementa como acessar arrays nativos de tipos de coleção (se você viu meu [. .NET Performance Optimization - Você Deve Definir o Tamanho Inicial para Tipos de Coleção], você sabe que a implementação subjacente de muitas estruturas de dados são os arrays). Assim, ele pode pular todo tipo de detecção e acessar diretamente o array original, que deve ser o mais rápido. O código é o seguinte:
Você pode ver que o código gerado pelo compilador é muito eficiente.
O acesso direto ao array subjacente é muito perigoso, você precisa saber o que está fazendo com cada linha de código e ter testes suficientes. Os resultados do benchmark são os seguintes:
UauUsar o CollectionsMarshal é 79% mais rápido do que usar foreach, mas essa deveria ser a razão da otimização do JIT, não há grande diferença entre usar foreach e o loop de palavras-chave for Span.
resumo
Hoje falei com você sobre como percorrer rapidamente a coleção de Listas e, na maioria dos casos, é recomendado usar a palavra-chave foreach, que possui tanto verificação de overflow quanto controle multithread de número de versão, o que pode facilitar a escrita do código correto.
Se você precisa de alto desempenho e volumes de dados grandes, recomenda-se usar o for e o CollectionsMarshal.AsSpan diretamente para percorrer a coleção.
Link do código-fonte deste artigo:
O login do hiperlink está visível.
Link original:O login do hiperlink está visível.
|