Rendimiento en .NET Core

¡Hola a todos! Este artículo es una recopilación de las Mejores Prácticas que mis colegas y yo hemos estado aplicando durante mucho tiempo en diferentes proyectos.
Información sobre la máquina en la que se efectuaron los cálculos:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 núcleos lógicos y 4 físicos
.NET Core SDK=3.0.100
[Host]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
Core: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
Core: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
Trabajo=Core Runtime=Core
ToList vs ToArray y Ciclos
Tenía planeado preparar esta información con el lanzamiento de .NET Core 3.0, pero me adelantaron, no quiero robar la gloria de otros ni copiar información ajena, así que simplemente indicaré .
Solo quiero presentarles mis mediciones y resultados, he añadido en ellos ciclos inversos para quienes disfrutan del estilo de programación 'C++'.
Código:
public class Bench
{
private List _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 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 0; i--)
{
total += _array[i];
}
return total;
}
}
La velocidad de operación en .NET Core 2.2 y 3.0 es casi idéntica. Esto es lo que logré obtener en .NET Core 3.0:


Podemos concluir que el procesamiento cíclico de una colección de tipo Array es más rápido, gracias a sus optimizaciones internas y la asignación explícita del tamaño de la colección. También vale la pena recordar que la colección de tipo List tiene sus ventajas y debes usar la colección adecuada según los cálculos necesarios. Incluso al escribir la lógica para trabajar con ciclos, no olvides que es un loop normal y también está sujeto a posibles optimizaciones. En Habr hace tiempo se publicó un artículo: . Aún es relevante y se recomienda su lectura.
Lanzar
Hace un año trabajé en una empresa en un proyecto legado, en el que se manejaba la validación de campos a través de la construcción try-catch-throw. Ya entonces entendía que esta lógica de negocio no era saludable, así que intentaba evitar esta construcción siempre que fuera posible. Pero analicemos por qué es malo manejar errores de esta manera. Escribí un pequeño código para comparar dos enfoques y grabé benchmarks de 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 son similares ( .NET Core 3.0 ):


El try catch complica la comprensión del código y aumenta el tiempo de ejecución de tu programa. Pero si necesitas esta construcción, no incluyas aquellas líneas de código de las que no se espera un manejo de errores; esto facilitará la comprensión del código. De hecho, no es tanto el manejo de excepciones lo que carga el sistema, sino el lanzamiento de errores a través de la construcción throw new Exception.
Lanzar excepciones es más lento que cualquier clase que recoja el error en el formato adecuado. Si estás procesando un formulario o algún tipo de datos y sabes claramente qué error debe ser manejado, ¿por qué no gestionarlo?
No debe escribir la construcción throw new Exception() si esta situación no es excepcional. ¡El manejo y lanzamiento de excepciones es muy costoso!
ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant
En mis 5 años de experiencia trabajando en la plataforma .NET, he encontrado muchos proyectos que utilizaban la comparación de cadenas. También he visto la siguiente situación: había una solución empresarial con muchos proyectos, cada uno de los cuales realizaba comparaciones de cadenas de manera diferente. Pero, ¿qué debería usarse y cómo unificarlo? En el libro CLR via C# de Richter, leí información sobre que el método ToUpperInvariant() es más rápido que ToLowerInvariant().
Extracto del libro:

Por supuesto, no lo creí y decidí realizar algunas pruebas en ese entonces en .NET Framework y el resultado me sorprendió: más del 15% de aumento en el rendimiento. Al llegar al trabajo la mañana siguiente, mostré los datos a mi supervisor y les di acceso al código fuente. Después de eso, 2 de 14 proyectos se modificaron para los nuevos puntos de referencia, y considerando que estos dos proyectos eran para procesar enormes tablas de Excel, el resultado fue más que significativo para el producto.
También les presento mediciones para diferentes versiones de .NET Core, para que cada uno de ustedes pueda elegir la solución más óptima. Y solo quiero añadir 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();
}


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


Compilación en niveles
En mi artículo anterior describí brevemente esta funcionalidad, y ahora me gustaría corregir y complementar mis palabras. La compilación multinivel acelera el tiempo de inicio de su solución, pero a cambio sacrifica el hecho de que partes de su código se compilarán en una versión más optimizada en segundo plano, lo que puede llevar a ligeros costos adicionales. Con la llegada de .NET Core 3.0, se redujo el tiempo de compilación de proyectos con compilación por niveles habilitada y se arreglaron los errores relacionados con esta tecnología. Anteriormente, esta tecnología causaba errores en las primeras solicitudes en ASP.NET Core y bloqueos durante la primera compilación en modo de compilación multinivel. Actualmente, en .NET Core 3.0 está habilitada por defecto, pero puede desactivarse si lo desea. Si ocupa un puesto de team lead, senior, middle o es líder de departamento, debe entender que el rápido desarrollo de un proyecto incrementa el valor del equipo y esta tecnología le permitirá ahorrar tiempo tanto a los desarrolladores como al proyecto en sí.
.NET mejora
Actualice su versión de .NET Framework / .NET Core. A menudo, cada nueva versión proporciona un aumento adicional en el rendimiento y añade nuevas funciones.
¿Pero cuáles son exactamente las ventajas? Vamos a revisar algunas de ellas:
- En .NET Core 3.0 se introdujeron imágenes R2R, que permitirán reducir el tiempo de inicio de las aplicaciones .NET Core.
- Desde la versión 2.2, se introdujo la Compilación por niveles, gracias a la cual los programadores dedicarán menos tiempo a iniciar el proyecto.
- Soporte para nuevos estándares .NET Standard.
- Soporte para la nueva versión del lenguaje de programación.
- Optimización; con cada nueva versión, la optimización de las bibliotecas base mejora, como Collection/Struct/Stream/String/Regex y muchas más. Si está pasando de .NET Framework a .NET Core, obtendrá un gran aumento en el rendimiento de manera inmediata. Para ilustrar esto, adjunto un enlace a algunas de las optimizaciones que se han añadido en .NET Core 3.0:

Conclusión
Al escribir código, es importante prestar atención a diferentes aspectos de su proyecto y usar las funciones de su lenguaje de programación y plataforma para alcanzar el mejor resultado. Estaré encantado si comparte sus conocimientos relacionados con la optimización en .NET.
Fuente: habr.com
