Rendimiento en .NET Core

Rendimiento en .NET Core

Rendimiento en .NET Core

¡Hola a todos! Este artículo es una colección de mejores prácticas que mis colegas y yo hemos estado utilizando durante mucho tiempo cuando trabajamos en diferentes proyectos.

Información sobre la máquina en la que se realizaron los cálculos:BenchmarkDotNet=v0.11.5, sistema operativo=Windows 10.0.18362
CPU Intel Core i5-8250U 1.60 GHz (Kaby Lake R), 1 CPU, 8 núcleos lógicos y 4 físicos
SDK de .NET Core=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

Trabajo=Núcleo Tiempo de ejecución=Núcleo

ToList vs ToArray y ciclos


Planeé preparar esta información con el lanzamiento de .NET Core 3.0, pero se me adelantaron, no quiero robar la gloria de otra persona y copiar la información de otras personas, así que solo señalaré enlace a un buen artículo donde se describe la comparación en detalle.

Por mi parte, solo quiero presentarles mis mediciones y resultados; les agregué bucles inversos para los amantes del “estilo C++” de escribir bucles.

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

Las velocidades de rendimiento en .NET Core 2.2 y 3.0 son casi idénticas. Esto es lo que pude obtener en .NET Core 3.0:

Rendimiento en .NET Core

Rendimiento en .NET Core

Podemos concluir que el procesamiento iterativo de una colección Array es más rápido debido a sus optimizaciones internas y su asignación explícita del tamaño de la colección. También vale la pena recordar que una colección List tiene sus propias ventajas y debes usar la colección correcta según los cálculos requeridos. Incluso si escribe lógica para trabajar con bucles, no olvide que se trata de un bucle normal y también está sujeto a una posible optimización del bucle. Hace bastante tiempo se publicó un artículo en habr: https://habr.com/ru/post/124910/. Sigue siendo una lectura relevante y recomendada.

Lanzar

Hace un año, trabajé en una empresa en un proyecto heredado, en ese proyecto era normal procesar la validación de campo a través de una construcción try-catch-throw. Entonces ya entendí que esta era una lógica de negocios poco saludable para el proyecto, así que siempre que fue posible intenté no utilizar ese diseño. Pero averigüemos por qué el enfoque para manejar errores con una construcción de este tipo es malo. Escribí un pequeño código para comparar los dos enfoques e hice puntos de referencia 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;
        }

Los resultados en .NET Core 3.0 y Core 2.2 tienen un resultado similar (.NET Core 3.0):

Rendimiento en .NET Core

Rendimiento en .NET Core

Try catch hace que el código sea más difícil de entender y aumenta el tiempo de ejecución de su programa. Pero si necesita esta construcción, no debe insertar aquellas líneas de código que no se espera que manejen errores; esto hará que el código sea más fácil de entender. De hecho, no es tanto el manejo de excepciones lo que carga el sistema, sino más bien el lanzamiento de errores a través de la construcción throw new Exception.

Lanzar excepciones es más lento que alguna clase que recopile el error en el formato requerido. Si estás procesando un formulario o algunos datos y tienes claro cuál debe ser el error, ¿por qué no procesarlo?

No debe escribir una construcción throw new Exception() si esta situación no es excepcional. ¡¡¡Manejar y lanzar una excepción es muy costoso!!!

HaciaInferior, HaciaInferiorInvariante, HaciaSuperior, HaciaUpperInvariante

Durante mis 5 años de experiencia trabajando en la plataforma .NET, me he encontrado con muchos proyectos que utilizaban la coincidencia de cadenas. También vi la siguiente imagen: había una solución empresarial con muchos proyectos, cada uno de los cuales realizaba comparaciones de cadenas de manera diferente. Pero ¿qué se debe utilizar y cómo unificarlo? En el libro CLR vía C# de Richter, leí información de que el método ToUpperInvariant() es más rápido que ToLowerInvariant().

Extracto del libro:

Rendimiento en .NET Core

Por supuesto, no lo creí y decidí ejecutar algunas pruebas en .NET Framework y el resultado me sorprendió: un aumento de rendimiento de más del 15%. Luego, al llegar al trabajo a la mañana siguiente, mostré estas medidas a mis superiores y les di acceso al código fuente. Después de esto, 2 de 14 proyectos fueron modificados para acomodar las nuevas medidas, y considerando que estos dos proyectos existían para procesar enormes tablas de Excel, el resultado fue más que significativo para el producto.

También les presento medidas para diferentes versiones de .NET Core, para que cada uno de ustedes pueda elegir la solución más óptima. Y solo quiero agregar que en la empresa donde trabajo usamos ToUpper() para comparar cadenas.

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

Rendimiento en .NET Core

Rendimiento en .NET Core

En .NET Core 3.0, el aumento para cada uno de estos métodos es ~x2 y equilibra las implementaciones entre sí.

Rendimiento en .NET Core

Rendimiento en .NET Core

Compilación de niveles

En mi último artículo describí brevemente esta funcionalidad, me gustaría corregir y complementar mis palabras. La compilación multinivel acelera el tiempo de inicio de su solución, pero sacrifica que partes de su código se compilarán en una versión más optimizada en segundo plano, lo que puede generar una pequeña sobrecarga. Con la llegada de NET Core 3.0, el tiempo de compilación para proyectos con compilación de niveles habilitada disminuyó y se corrigieron los errores asociados con esta tecnología. Anteriormente, esta tecnología provocaba errores en las primeras solicitudes en ASP.NET Core y se congelaba durante la primera compilación en modo de compilación multinivel. Actualmente está habilitado de forma predeterminada en .NET Core 3.0, pero puede deshabilitarlo si lo desea. Si estás en el puesto de líder de equipo, senior, middle o eres el jefe de un departamento, entonces debes comprender que el desarrollo rápido de proyectos aumenta el valor del equipo y esta tecnología te permitirá ahorrar tiempo a ambos desarrolladores. y el tiempo del proyecto en sí.

Subir de nivel .NET

Actualice su versión de .NET Framework/.NET Core. A menudo, cada nueva versión proporciona mejoras de rendimiento adicionales y agrega nuevas funciones.

Pero ¿cuáles son exactamente los beneficios? Veamos algunos de ellos:

  • .NET Core 3.0 introdujo imágenes R2R que reducirán el tiempo de inicio de las aplicaciones .NET Core.
  • Con la versión 2.2, apareció Tier Compilation, gracias a la cual los programadores dedicarán menos tiempo a ejecutar un proyecto.
  • Soporte para nuevos estándares .NET.
  • Soporte para una nueva versión del lenguaje de programación.
  • Optimización, con cada nueva versión mejora la optimización de las bibliotecas base Collection/Struct/Stream/String/Regex y mucho más. Si está migrando de .NET Framework a .NET Core, obtendrá un gran aumento de rendimiento desde el primer momento. Como ejemplo, adjunto un enlace a algunas de las optimizaciones que se agregaron a .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Rendimiento en .NET Core

Conclusión

Al escribir código, vale la pena prestar atención a los diferentes aspectos de su proyecto y utilizar las características de su lenguaje y plataforma de programación para lograr el mejor resultado. Me encantaría que compartiera sus conocimientos relacionados con la optimización en .NET.

Enlace a github

Fuente: habr.com

Añadir un comentario