Caso práctico: Guía para principiantes para optimizar el código y reducir los costos de proceso (C#, Visual Basic, C++, F#)
Reducir el tiempo de proceso significa reducir los costos, por lo que optimizar el código puede ahorrar dinero. En este caso práctico se usa una aplicación de ejemplo con problemas de rendimiento para demostrar cómo usar herramientas de generación de perfiles para mejorar la eficacia. Si desea comparar las herramientas de generación de perfiles, consulte ¿Qué herramienta debo elegir?
En este caso práctico se tratan estos temas:
- La importancia de la optimización del código y su impacto en la reducción de los costos de proceso.
- Cómo usar las herramientas de generación de perfiles de Visual Studio para analizar el rendimiento de las aplicaciones.
- Cómo interpretar los datos proporcionados por estas herramientas para identificar cuellos de botella en el rendimiento.
- Cómo aplicar estrategias prácticas para optimizar el código, centrarse en el uso de CPU, la asignación de memoria y las interacciones de la base de datos.
Siga estos pasos y aplique estas técnicas a sus propias aplicaciones para que sean más eficaces y rentables.
Caso práctico de optimización
La aplicación de ejemplo que se examina en este caso práctico es una aplicación .NET que ejecuta consultas en una base de datos de blogs y entradas de blog. Utiliza Entity Framework, una ORM (asignación relacional de objetos) popular para .NET, para interactuar con una base de datos local de SQLite. La aplicación está estructurada para ejecutar un gran número de consultas, simulando un escenario real en el que es posible que se requiera una aplicación .NET para controlar las tareas de recuperación de datos extensas. La aplicación de ejemplo es una versión modificada del ejemplo de introducción de Entity Framework.
El problema de rendimiento principal con la aplicación de ejemplo radica en cómo administra los recursos de proceso e interactúa con la base de datos. La aplicación tiene un cuello de botella de rendimiento que afecta significativamente a su eficiencia y, en consecuencia, los costos de proceso asociados a su ejecución. El problema incluye los siguientes síntomas:
uso elevado de CPU: las aplicaciones pueden realizar cálculos ineficazes o tareas de procesamiento de una manera que consume innecesariamente una gran cantidad de recursos de CPU. Esto puede provocar tiempos de respuesta lentos y un aumento de los costos operativos.
Asignación de memoria ineficaz: las aplicaciones a veces pueden encontrarse con problemas relacionados con el uso y la asignación de memoria. En las aplicaciones .NET, la administración de memoria ineficaz puede provocar un aumento de la recolección de elementos no utilizados, lo que a su vez puede afectar al rendimiento de la aplicación.
Sobrecargas de interacción de la base de datos: las aplicaciones que ejecutan un gran número de consultas en una base de datos pueden experimentar cuellos de botella relacionados con las interacciones de la base de datos. Esto incluye consultas ineficaces, llamadas excesivas a la base de datos y un uso deficiente de las funcionalidades de Entity Framework, lo que puede reducir el rendimiento.
El caso práctico tiene como objetivo abordar estos problemas mediante el uso de las herramientas de generación de perfiles de Visual Studio para analizar el rendimiento de la aplicación. Al comprender dónde y cómo se puede mejorar el rendimiento de la aplicación, los desarrolladores pueden implementar optimizaciones para reducir el uso de cpu, mejorar la eficacia de la asignación de memoria, simplificar las interacciones de la base de datos y optimizar el uso de recursos. El objetivo final es mejorar el rendimiento general de la aplicación, lo que hace que sea más eficaz y rentable ejecutarse.
Desafío
La solución de los problemas de rendimiento de la aplicación .NET de ejemplo presenta varios desafíos. Estos desafíos se deben a la complejidad de diagnosticar los cuellos de botella en el rendimiento. Los principales desafíos para solucionar los problemas descritos son los siguientes:
Diagnóstico de cuellos de botella de rendimiento: una de las principales dificultades es identificar con precisión las causas principales de los problemas de rendimiento. El uso elevado de la CPU, la asignación de memoria ineficaz y las sobrecargas de interacción de la base de datos pueden tener varios factores de contribución. Los desarrolladores deben usar herramientas de generación de perfiles de forma eficaz para diagnosticar estos problemas, lo que requiere comprender cómo funcionan estas herramientas y cómo interpretar su salida.
restricciones de recursos y conocimientos: por último, los equipos pueden enfrentar restricciones relacionadas con el conocimiento, la experiencia y los recursos. La generación de perfiles y la optimización de una aplicación requieren aptitudes y experiencia específicas, y no todos los equipos pueden tener acceso inmediato a estos recursos.
Abordar estos desafíos requiere un enfoque estratégico que combina el uso eficaz de herramientas de generación de perfiles, conocimientos técnicos y planificación y pruebas cuidadosas. El caso práctico tiene como objetivo guiar a los desarrolladores a través de este proceso, proporcionando estrategias e información para superar estos desafíos y mejorar el rendimiento de la aplicación.
Estrategia
Esta es una vista general del enfoque en este caso práctico:
- Iniciamos la investigación tomando un seguimiento de uso de CPU. La herramienta uso de CPU de Visual Studio suele ser útil para comenzar las investigaciones de rendimiento y optimizar el código para reducir el costo.
- A continuación, para obtener información adicional para ayudar a aislar problemas o mejorar el rendimiento, recopilamos un seguimiento mediante una de las otras herramientas de generación de perfiles. Por ejemplo:
- Echemos un vistazo al uso de memoria. En .NET, intentamos primero la herramienta de asignación de objetos de .NET . (En el caso de .NET o C++, puede examinar la herramienta Uso de memoria como alternativa).
- Para ADO.NET o Entity Framework, podemos usar la herramienta de base de datos de para examinar consultas SQL, tiempo de consulta preciso y mucho más.
La recopilación de datos requiere las siguientes tareas:
- Configuración de la aplicación para una versión de lanzamiento.
- Selección de la herramienta Uso de CPU en el Generador de perfiles de rendimiento (Alt+F2). (Los pasos posteriores implican algunas de las otras herramientas).
- En el Generador de perfiles de rendimiento, inicie la aplicación y recopile un seguimiento.
Inspección de áreas de uso elevado de CPU
Después de recopilar un seguimiento con la herramienta Uso de CPU y cargarlo en Visual Studio, primero comprobamos la página de informe .diagsession inicial que muestra los datos resumidos. Utilice el vínculo Abrir detalles en el informe.
En la vista de detalles del informe, abra la vista Árbol de llamadas. La ruta de código con más alto uso de CPU en la aplicación se denomina camino crítico . El icono de llama de la ruta de acceso activa () puede ayudar a identificar rápidamente los problemas de rendimiento que podrían mejorarse.
En la vista Árbol de llamadas, puede ver un uso elevado de la CPU para el método GetBlogTitleX
en la aplicación, usando aproximadamente un 60 % del uso compartido de CPU de la aplicación. Sin embargo, el valor de Solo CPU para GetBlogTitleX
es bajo, solo alrededor del 0,10 %. A diferencia de CPU total, el valor de Solo CPU excluye el tiempo invertido en otras funciones, por lo que sabemos que debemos buscar más abajo en la vista de árbol de llamadas para el cuello de botella real.
GetBlogTitleX
realiza llamadas externas a dos archivos de la DLL de LINQ, que usan la mayor parte del tiempo de CPU, como se evidencia en los valores de Solo CPU muy altos. Esta es la primera pista de que una consulta LINQ podría ser un área para optimizar.
Para obtener un árbol de llamadas visualizado y una vista diferente de los datos, abra la vista Flame Graph. (O bien, haga clic con el botón derecho en GetBlogTitleX
y elija Ver en Flame Graph). Aquí de nuevo, parece que el método GetBlogTitleX
es responsable de una gran cantidad del uso de CPU de la aplicación (que se muestra en amarillo). Las llamadas externas a las DLL de LINQ se muestran debajo del cuadro GetBlogTitleX
y consumen todo el tiempo de CPU del método.
Recopilación de datos adicionales
A menudo, otras herramientas pueden proporcionar información adicional para ayudar al análisis y aislar el problema. En este caso práctico, adoptamos el siguiente enfoque:
- En primer lugar, examine el uso de memoria. Puede haber una correlación entre un uso elevado de CPU y un uso elevado de memoria, por lo que puede resultar útil examinar ambos para aislar el problema.
- Dado que hemos identificado los archivos DLL de LINQ, también veremos la herramienta Base de datos.
Comprobación del uso de memoria
Para ver lo que sucede con la aplicación en términos de uso de memoria, recopilamos un seguimiento mediante la herramienta de asignación de objetos de .NET (para C++, puede usar la herramienta Uso de memoria en su lugar). La vista Árbol de llamadas del seguimiento de la memoria muestra la ruta de acceso activa y le ayuda a identificar un área de uso elevado de la memoria. No hay sorpresa en este momento, el método GetBlogTitleX
parece estar generando una gran cantidad de objetos! De hecho, hay más de 900 000 asignaciones de objetos.
La mayoría de los objetos creados son cadenas, matrices de objetos e Int32s. Es posible que podamos ver cómo se generan estos tipos examinando el código fuente.
Comprobación de la consulta en la herramienta Base de datos
En el Generador de perfiles de rendimiento, seleccionamos la herramienta Base de datos en lugar de Uso de CPU (o, ambas). Cuando hayamos recopilado un seguimiento, abra la pestaña Consultas en la página de diagnósticos. En la pestaña Consultas del seguimiento de la base de datos, puede ver que la primera fila muestra la consulta más larga, 2446 ms. La columna Registros muestra cuántos registros lee la consulta. Puede usar esta información para la comparación posterior.
Al examinar la instrucción SELECT
generada por LINQ en la columna Consulta, identificamos la primera fila como la consulta asociada al método GetBlogTitleX
. Para ver la cadena de consulta completa, expanda el ancho de columna. La cadena de consulta completa es:
SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"
Tenga en cuenta que la aplicación está recuperando muchos valores de columna aquí, quizás más de lo que necesitamos. Echemos un vistazo al código fuente.
Optimización del código
Es el momento de echar un vistazo al código fuente GetBlogTitleX
. En la herramienta Base de datos, haga clic con el botón derecho en la consulta y elija Ir al archivo de origen. En el código fuente de GetBlogTitleX
, encontramos el código siguiente que usa LINQ para leer la base de datos.
foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
{
foreach (var post in blog.Posts)
{
if (post.Author == "Fred Smith")
{
Console.WriteLine($"Post: {post.Title}");
}
}
}
Este código usa bucles foreach
para buscar la base de datos de los blogs con "Fred Smith" como autor. Al examinarlo, puede ver que se generan muchos objetos en memoria: una nueva matriz de objetos para cada blog de la base de datos, cadenas asociadas para cada dirección URL y valores para las propiedades contenidas en las entradas, como el identificador de blog.
Realizamos una pequeña investigación y encontramos algunas recomendaciones comunes sobre cómo optimizar las consultas LINQ. Como alternativa, podemos ahorrar tiempo y dejar que Copilot hacer la investigación para nosotros.
Si usamos Copilot, seleccionamos Preguntar a Copilot en el menú contextual y escribimos la siguiente pregunta:
Can you make the LINQ query in this method faster?
Sugerencia
Puede usar comandos con barra como /optimize para ayudar a formar buenas preguntas para el Copilot.
En este ejemplo, Copilot proporciona los siguientes cambios de código sugeridos, junto con una explicación.
public void GetBlogTitleX()
{
var posts = db.Posts
.Where(post => post.Author == "Fred Smith")
.Select(post => post.Title)
.ToList();
foreach (var postTitle in posts)
{
Console.WriteLine($"Post: {postTitle}");
}
}
Este código incluye varios cambios para ayudar a optimizar la consulta:
- Se agregó la cláusula
Where
y se eliminó uno de los buclesforeach
. - Se proyecta solo la propiedad Title en la instrucción
Select
, que es todo lo que necesitamos en este ejemplo.
A continuación, se vuelve a probar mediante las herramientas de generación de perfiles.
Resultados
Después de actualizar el código, vuelva a ejecutar la herramienta Uso de CPU para recopilar un seguimiento. La vista Árbol de llamadas muestra que GetBlogTitleX
solo se ejecuta 1754 ms, y usa un 37 % del total de la CPU de la aplicación, una mejora significativa del 59 %.
Cambie a la vista Flame Graph para ver otra visualización que muestra la mejora. En esta vista, GetBlogTitleX
también usa una parte más pequeña de la CPU.
Compruebe los resultados en el seguimiento de la herramienta base de datos y solo se leen dos registros con esta consulta, en lugar de 100.000. Además, la consulta se simplifica mucho y elimina el LEFT JOIN innecesario que se generó anteriormente.
A continuación, volvemos a revisar los resultados en la herramienta de asignación de objetos de .NET y vemos que GetBlogTitleX
es responsable de solo 56.000 asignaciones de objetos, lo que representa una reducción de casi un 95%% desde 900.000.
Iterar
Es posible que sea necesario realizar varias optimizaciones y podemos seguir iterando con los cambios de código para ver qué cambios mejoran el rendimiento y ayudan a reducir el costo de proceso.
Pasos siguientes
Los siguientes artículos y entradas de blog proporcionan más información para ayudarle a aprender a usar las herramientas de rendimiento de Visual Studio de forma eficaz.
- caso práctico: aislar un problema de rendimiento
- caso práctico: doble rendimiento en menos de 30 minutos
- mejorar el rendimiento de Visual Studio con la nueva herramienta de instrumentación