Desempenho no .NET Core

Desempenho no .NET Core

Desempenho no .NET Core

Olá a todos! Este artigo é uma coleção de práticas recomendadas que meus colegas e eu usamos há muito tempo ao trabalhar em diferentes projetos.

Informações sobre a máquina na qual os cálculos foram realizados:BenchmarkDotNet=v0.11.5, SO=Windows 10.0.18362
CPU Intel Core i5-8250U 1.60 GHz (Kaby Lake R), 1 CPU, 8 núcleos lógicos e 4 físicos
SDK do .NET Core=3.0.100
[Host]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT de 64 bits
Núcleo: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT de 64 bits
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT de 64 bits
Núcleo: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT de 64 bits

Trabalho = Núcleo Tempo de execução = Núcleo

ToList vs ToArray e Ciclos


Planejei preparar essas informações com o lançamento do .NET Core 3.0, mas eles me venceram, não quero roubar a glória de outra pessoa e copiar as informações de outras pessoas, então vou apenas salientar link para um bom artigo onde a comparação é descrita em detalhes.

Em meu nome, quero apenas apresentar a vocês minhas medições e resultados; adicionei loops reversos a eles para os amantes do “estilo C++” de escrever loops.

Código:

public class Bench
    {
        private List<int> _list;
        private int[] _array;

        [Params(100000, 10000000)] public int N;

        [GlobalSetup]
        public void Setup()
        {
            const int MIN = 1;
            const int MAX = 10;
            Random random = new Random();
            _list = Enumerable.Repeat(0, N).Select(i => random.Next(MIN, MAX)).ToList();
            _array = _list.ToArray();
        }

        [Benchmark]
        public int ForList()
        {
            int total = 0;
            for (int i = 0; i < _list.Count; i++)
            {
                total += _list[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForListFromEnd()
        {
            int total = 0;t
            for (int i = _list.Count-1; i > 0; i--)
            {
                total += _list[i];
            }

            return total;
        }

        [Benchmark]
        public int ForeachList()
        {
            int total = 0;
            foreach (int i in _list)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForeachArray()
        {
            int total = 0;
            foreach (int i in _array)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForArray()
        {
            int total = 0;
            for (int i = 0; i < _array.Length; i++)
            {
                total += _array[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForArrayFromEnd()
        {
            int total = 0;
            for (int i = _array.Length-1; i > 0; i--)
            {
                total += _array[i];
            }

            return total;
        }
    }

As velocidades de desempenho no .NET Core 2.2 e 3.0 são quase idênticas. Aqui está o que consegui obter no .NET Core 3.0:

Desempenho no .NET Core

Desempenho no .NET Core

Podemos concluir que o processamento iterativo de uma coleção Array é mais rápido devido às suas otimizações internas e à alocação explícita do tamanho da coleção. Também vale lembrar que uma coleção List tem suas vantagens e você deve usar a coleção certa dependendo dos cálculos necessários. Mesmo se você escrever lógica para trabalhar com loops, não esqueça que este é um loop comum e também está sujeito a uma possível otimização de loop. Um artigo foi publicado no habr há muito tempo: https://habr.com/ru/post/124910/. Ainda é uma leitura relevante e recomendada.

Jogar

Há um ano trabalhei em uma empresa em um projeto legado, nesse projeto era normal processar a validação de campo através de uma construção try-catch-throw. Eu já entendi então que essa lógica de negócio não era saudável para o projeto, então sempre que possível tentei não usar tal design. Mas vamos descobrir por que a abordagem para lidar com erros com tal construção é ruim. Escrevi um pequeno código para comparar as duas abordagens e fiz benchmarks para cada opção.

Código:

        public bool ContainsHash()
        {
            bool result = false;
            foreach (var file in _files)
            {
                var extension = Path.GetExtension(file);
                if (_hash.Contains(extension))
                    result = true;
            }

            return result;
        }

        public bool ContainsHashTryCatch()
        {
            bool result = false;
            try
            {
                foreach (var file in _files)
                {
                    var extension = Path.GetExtension(file);
                    if (_hash.Contains(extension))
                        result = true;
                }
                
                if(!result) 
                    throw new Exception("false");
            }
            catch (Exception e)
            {
                result = false;
            }

            return result;
        }

Os resultados no .NET Core 3.0 e Core 2.2 têm um resultado semelhante (.NET Core 3.0):

Desempenho no .NET Core

Desempenho no .NET Core

Try catch torna o código mais difícil de entender e aumenta o tempo de execução do seu programa. Mas se você precisar dessa construção, não deverá inserir aquelas linhas de código que não devem lidar com erros - isso tornará o código mais fácil de entender. Na verdade, não é tanto o tratamento de exceções que carrega o sistema, mas sim o lançamento dos próprios erros por meio da construção throw new Exception.

Lançar exceções é mais lento do que alguma classe que coletará o erro no formato necessário. Se você está processando um formulário ou alguns dados e sabe claramente qual deve ser o erro, por que não processá-lo?

Você não deve escrever uma construção throw new Exception() se esta situação não for excepcional. Manipular e lançar uma exceção é muito caro!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

Ao longo dos meus 5 anos de experiência trabalhando na plataforma .NET, encontrei muitos projetos que usavam correspondência de strings. Também vi a seguinte imagem: havia uma solução empresarial com muitos projetos, cada um realizando comparações de strings de maneira diferente. Mas o que deve ser usado e como unificá-lo? No livro CLR via C# de Richter, li informações de que o método ToUpperInvariant() é mais rápido que ToLowerInvariant().

Trecho do livro:

Desempenho no .NET Core

Claro, eu não acreditei e resolvi fazer alguns testes no .NET Framework e o resultado me chocou - mais de 15% de aumento de desempenho. Então, ao chegar ao trabalho na manhã seguinte, mostrei essas medidas aos meus superiores e dei-lhes acesso ao código-fonte. Depois disso, 2 dos 14 projetos foram alterados para acomodar as novas medidas, e considerando que esses dois projetos existiam para processar enormes tabelas Excel, o resultado foi mais que significativo para o produto.

Também apresento medidas para diferentes versões do .NET Core, para que cada um de vocês possa escolher a solução mais ideal. E só quero acrescentar que na empresa onde trabalho usamos ToUpper() para comparar strings.

Código:

public const string defaultString =  "VXTDuob5YhummuDq1PPXOHE4PbrRjYfBjcHdFs8UcKSAHOCGievbUItWhU3ovCmRALgdZUG1CB0sQ4iMj8Z1ZfkML2owvfkOKxBCoFUAN4VLd4I8ietmlsS5PtdQEn6zEgy1uCVZXiXuubd0xM5ONVZBqDu6nOVq1GQloEjeRN8jXrj0MVUexB9aIECs7caKGddpuut3";

        [Benchmark]
        public bool ToLower()
        {
            return defaultString.ToLower() == defaultString.ToLower();
        }

        [Benchmark]
        public bool ToLowerInvariant()
        {
            return defaultString.ToLowerInvariant() == defaultString.ToLowerInvariant();
        }

        [Benchmark]
        public bool ToUpper()
        {
            return defaultString.ToUpper() == defaultString.ToUpper();
        }

        [Benchmark]
        public bool ToUpperInvariant()
        {
            return defaultString.ToUpperInvariant() == defaultString.ToUpperInvariant();
        }

Desempenho no .NET Core

Desempenho no .NET Core

No .NET Core 3.0, o aumento para cada um desses métodos é ~x2 e equilibra as implementações entre si.

Desempenho no .NET Core

Desempenho no .NET Core

Compilação de camadas

No meu último artigo descrevi brevemente esta funcionalidade, gostaria de corrigir e complementar minhas palavras. A compilação multinível acelera o tempo de inicialização da sua solução, mas você sacrifica o fato de que partes do seu código serão compiladas em uma versão mais otimizada em segundo plano, o que pode introduzir uma pequena sobrecarga. Com o advento do NET Core 3.0, o tempo de compilação para projetos com compilação em camadas habilitada diminuiu e os bugs associados a essa tecnologia foram corrigidos. Anteriormente, essa tecnologia gerava erros nas primeiras solicitações no ASP.NET Core e travava durante a primeira compilação no modo de compilação multinível. Atualmente está habilitado por padrão no .NET Core 3.0, mas você pode desativá-lo se desejar. Se você está na posição de líder de equipe, sênior, intermediário ou chefe de departamento, então deve entender que o rápido desenvolvimento do projeto aumenta o valor da equipe e esta tecnologia permitirá que você economize tempo para ambos os desenvolvedores e o tempo do projeto em si.

Nível .NET subindo

Atualize sua versão do .NET Framework/.NET Core. Muitas vezes, cada nova versão proporciona ganhos adicionais de desempenho e adiciona novos recursos.

Mas quais são exatamente os benefícios? Vejamos alguns deles:

  • O .NET Core 3.0 introduziu imagens R2R que reduzirão o tempo de inicialização dos aplicativos .NET Core.
  • Com a versão 2.2 surgiu o Tier Compilation, graças ao qual os programadores gastarão menos tempo lançando um projeto.
  • Suporte para novos padrões .NET.
  • Suporte para uma nova versão da linguagem de programação.
  • Otimização, a cada nova versão a otimização das bibliotecas base Collection/Struct/Stream/String/Regex e muito mais melhora. Se você estiver migrando do .NET Framework para o .NET Core, obterá um grande aumento de desempenho imediatamente. Como exemplo, anexo um link para algumas das otimizações que foram adicionadas ao .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Desempenho no .NET Core

Conclusão

Ao escrever o código, vale a pena prestar atenção aos diferentes aspectos do seu projeto e utilizar os recursos da sua linguagem de programação e plataforma para obter o melhor resultado. Ficarei feliz se você compartilhar seu conhecimento relacionado à otimização em .NET.

Link para o github

Fonte: habr.com

Adicionar um comentário