Rendemento en .NET Core

Rendemento en .NET Core

Rendemento en .NET Core

Ola a todos! Este artigo é unha colección de Mellores Prácticas que os meus compañeiros e eu utilizamos dende hai moito tempo cando traballamos en diferentes proxectos.

Información sobre a máquina na que se realizaron os cálculos:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
CPU Intel Core i5-8250U 1.60 GHz (Kaby Lake R), 1 CPU, 8 núcleos lóxicos e 4 físicos
.NET Core SDK=3.0.100
[Anfitrión]: .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
[Anfitrión]: .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

Job=Núcleo Runtime=Núcleo

ToList vs ToArray e Cycles


Pensei preparar esta información co lanzamento de .NET Core 3.0, pero me superaron, non quero roubar a gloria doutra persoa e copiar a información doutras persoas, así que só indicarei ligazón a un bo artigo onde se describe detalladamente a comparación.

No meu propio nome, só quero presentarvos as miñas medicións e resultados; engadínlles bucles inversos para os amantes do "estilo C++" dos bucles de escritura.

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 rendemento en .NET Core 2.2 e 3.0 son case idénticas. Isto é o que puiden obter en .NET Core 3.0:

Rendemento en .NET Core

Rendemento en .NET Core

Podemos concluír que o procesamento iterativo dunha colección Array é máis rápido debido ás súas optimizacións internas e á asignación explícita do tamaño da colección. Tamén vale a pena lembrar que unha colección de Lista ten as súas propias vantaxes e debes usar a colección correcta dependendo dos cálculos necesarios. Aínda que escribas lóxica para traballar con bucles, non debes esquecer que este é un bucle común e tamén está suxeito a unha posible optimización dos bucles. Hai moito tempo publicouse un artigo en habr: https://habr.com/ru/post/124910/. Aínda é de lectura relevante e recomendable.

Bota

Hai un ano, traballei nunha empresa nun proxecto legado, nese proxecto era normal procesar a validación de campo mediante unha construción try-catch-throw. Xa entendín entón que iso era unha lóxica de negocio pouco saudable para o proxecto, así que sempre que foi posible tentei non usar tal deseño. Pero imos descubrir por que o enfoque para xestionar erros con tal construción é malo. Escribín un pequeno código para comparar os dous enfoques e fixen referencias para cada opción.

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 en .NET Core 3.0 e Core 2.2 teñen un resultado similar (.NET Core 3.0):

Rendemento en .NET Core

Rendemento en .NET Core

Try catch fai que o código sexa máis difícil de entender e aumenta o tempo de execución do programa. Pero se precisa esta construción, non debe inserir aquelas liñas de código que non se espera que xestionen erros; isto fará que o código sexa máis fácil de entender. De feito, non é tanto o manexo das excepcións o que carga o sistema, senón o lanzamento dos propios erros a través da nova construción de excepcións.

Lanzar excepcións é máis lento que algunha clase que recollerá o erro no formato necesario. Se estás procesando un formulario ou algúns datos e sabes claramente cal debe ser o erro, por que non o procesas?

Non debería escribir unha nova construción Exception() se esta situación non é excepcional. Manexar e lanzar unha excepción é moi caro!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

Ao longo dos meus 5 anos de experiencia traballando na plataforma .NET, atopeime con moitos proxectos que utilizaban a correspondencia de cadeas. Tamén vin a seguinte imaxe: había unha solución Enterprise con moitos proxectos, cada un dos cales realizaba comparacións de cadeas de forma diferente. Pero que se debe usar e como unificalo? No libro CLR vía C# de Richter, lin información de que o método ToUpperInvariant() é máis rápido que ToLowerInvariant().

Extracto do libro:

Rendemento en .NET Core

Por suposto, non o cría e decidín realizar algunhas probas en .NET Framework e o resultado sorprendeume: aumento de rendemento de máis do 15%. Despois, ao chegar ao traballo á mañá seguinte, mostrei estas medidas aos meus superiores e deille acceso ao código fonte. Despois disto, 2 de cada 14 proxectos foron modificados para acomodar as novas medicións, e tendo en conta que estes dous proxectos existían para procesar enormes táboas de Excel, o resultado foi máis que significativo para o produto.

Tamén vos presento medidas para diferentes versións de .NET Core, para que cada un de vós poida escoller a solución máis óptima. E só quero engadir que na empresa onde traballo, usamos ToUpper() para comparar cadeas.

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();
        }

Rendemento en .NET Core

Rendemento en .NET Core

En .NET Core 3.0, o aumento de cada un destes métodos é de ~x2 e equilibra as implementacións entre si.

Rendemento en .NET Core

Rendemento en .NET Core

Compilación de niveis

No meu último artigo describín brevemente esta funcionalidade, gustaríame corrixir e complementar as miñas palabras. A compilación de varios niveis acelera o tempo de inicio da túa solución, pero sacrificas que partes do teu código se compilarán nunha versión máis optimizada en segundo plano, o que pode introducir unha pequena sobrecarga. Coa chegada de NET Core 3.0, o tempo de construción dos proxectos coa compilación de niveis activada diminuíu e corrixíronse os erros asociados a esta tecnoloxía. Anteriormente, esta tecnoloxía provocou erros nas primeiras solicitudes en ASP.NET Core e conxelouse durante a primeira compilación en modo de compilación multinivel. Actualmente está activado por defecto en .NET Core 3.0, pero pode desactivalo se o desexa. Se estás no posto de xefe de equipo, sénior, intermedio ou es o xefe dun departamento, debes entender que o desenvolvemento rápido do proxecto aumenta o valor do equipo e esta tecnoloxía permitirache aforrar tempo para ambos os desenvolvedores. e o tempo do propio proxecto.

Sube de nivel .NET

Actualiza a túa versión de .NET Framework/.NET Core. A miúdo, cada nova versión proporciona beneficios adicionais de rendemento e engade novas funcións.

Pero cales son exactamente os beneficios? Vexamos algúns deles:

  • .NET Core 3.0 introduciu imaxes R2R que reducirán o tempo de inicio das aplicacións .NET Core.
  • Coa versión 2.2, apareceu Tier Compilation, grazas á cal os programadores dedicarán menos tempo a lanzar un proxecto.
  • Soporte para novos estándares .NET.
  • Soporte para unha nova versión da linguaxe de programación.
  • Optimización, con cada nova versión mellora a optimización das bibliotecas base Collection/Struct/Stream/String/Regex e moito máis. Se está a migrar de .NET Framework a .NET Core, obterá un gran aumento do rendemento. Como exemplo, adxunto unha ligazón a algunhas das optimizacións que se engadiron a .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Rendemento en .NET Core

Conclusión

Ao escribir código, paga a pena prestar atención a diferentes aspectos do seu proxecto e utilizar as funcións da súa linguaxe de programación e plataforma para conseguir o mellor resultado. Gustaríame que compartas os teus coñecementos relacionados coa optimización en .NET.

Ligazón a github

Fonte: www.habr.com

Engadir un comentario