Guía para inicio para optimizar el código y reducir los costos de proceso (C#, Visual Basic, C++, F#)

La reducción del tiempo de proceso significa reducir los costos, por lo que la optimización del código puede ahorrar dinero. En este artículo se muestra cómo puede usar varias herramientas de generación de perfiles, para ayudarle a realizar esta tarea.

En lugar de proporcionar instrucciones paso a paso, la intención aquí es mostrar cómo usar las herramientas de generación de perfiles de forma eficaz y cómo interpretar los datos. La herramienta Uso de CPU puede ayudarle a capturar y visualizar en qué partes de la aplicación se usan los recursos de proceso. Las vistas de Uso de CPU, como el árbol de llamadas y el gráfico de llamas, proporcionan una buena visualización gráfica de dónde se invierte el tiempo en la aplicación. Además, la información automática puede mostrar optimizaciones precisas que pueden tener un gran impacto. Otras herramientas de generación de perfiles pueden ayudarle también a aislar problemas. Para comparar herramientas, consulte ¿Qué herramienta debo usar?

Inicio de una investigación

  • Haga un seguimiento del uso de la CPU para iniciar la investigación. La herramienta Uso de CPU a menudo resulta útil para comenzar las investigaciones de rendimiento y optimizar el código, con el fin de reducir el costo.
  • A continuación, si desea información adicional que le ayude a aislar problemas o mejorar el rendimiento, considere la posibilidad de recopilar un seguimiento mediante alguna de las otras herramientas de generación de perfiles. Por ejemplo:
    • Examine el uso de la memoria. En .NET, pruebe primero la herramienta Asignación de objetos de .NET. Tanto en .NET como en C++, puede ver la herramienta Uso de memoria.
    • Si la aplicación usa la E/S de archivos, use la herramienta E/S de archivos.
    • Si usa ADO.NET o Entity Framework, puede probar la herramienta Base de datos para examinar consultas SQL, el tiempo de consulta preciso, etc.

Ejemplo de colección de datos

Las capturas de pantalla de ejemplo que se muestran en este artículo se basan en una aplicación .NET que ejecuta consultas en una base de datos de blogs y entradas de blog asociadas. Primero examinará el seguimiento del uso de la CPU para buscar oportunidades de optimizar el código y reducir el costo del proceso. Tras lograr una idea general, examinará los seguimientos de otras herramientas de generación de perfiles, ya que esto le ayudará a aislar problemas.

La recopilación de datos requiere los pasos siguientes (no se muestran aquí):

  • Establezca la aplicación en una versión de lanzamiento.
  • Seleccione 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

Para empezar, recopile un seguimiento con la herramienta Uso de CPU. Cuando se carguen los datos de diagnóstico, compruebe primero la página inicial del informe .diagsession que muestra la información principal y la ruta de acceso activa. La ruta de acceso activa muestra la ruta de acceso al código con el máximo uso de la CPU en la aplicación. Estas secciones pueden proporcionar sugerencias que le ayudarán a identificar rápidamente los problemas de rendimiento que puede mejorar.

La ruta de acceso activa también se puede ver en la vista Árbol de llamadas. Para abrir esta vista, use el vínculo Abrir detalles en el informe y, después, seleccione Árbol de llamadas.

Aquí, se vuelve a ver la ruta de acceso activa, que muestra un uso elevado de la CPU para el método GetBlogTitleX en la aplicación, con aproximadamente un 60 % de uso compartido de la CPU por parte 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.

Captura de pantalla de la vista Árbol de llamadas en la herramienta Uso de CPU.

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 que puede querer buscar en una consulta LINQ como un área para optimizar.

Captura de pantalla de la vista Árbol de llamadas en la herramienta de Uso de CPU con CPU propia resaltado.

Para obtener un árbol de llamadas visualizado y otra vista de los datos, cambie a la vista Grafo de llamas (selecciónelo en la misma lista que Árbol de llamadas). Una vez más, parece que el método GetBlogTitleX es el responsable de una gran cantidad del uso de la CPU de la aplicación (que se muestra en amarillo). Las llamadas externas a los archivos DLL de LINQ se muestran debajo del cuadro GetBlogTitleX y usan todo el tiempo de la CPU para el método.

Captura de pantalla de la vista Grafo de llamas en la herramienta Uso de CPU.

Recopilación de datos adicionales

A menudo, otras herramientas pueden proporcionar información adicional para ayudar al análisis y aislar el problema. Por ejemplo, como hemos identificado los archivos DLL de LINQ, primero probaremos la herramienta Base de datos. Puede seleccionar varias opciones de esta herramienta junto con Uso de CPU. Cuando haya recopilado un seguimiento, seleccione la pestaña Consultas de la página de diagnóstico.

En la pestaña Consultas del seguimiento de base de datos, puede ver que la primera fila muestra la consulta más larga, 2446 ms. La columna Registros muestra el número de registros que lee la consulta. Esta información se puede usar para realizar una comparación posterior.

Captura de pantalla de las consultas de base de datos en la herramienta Base de datos.

Al examinar la instrucción SELECT generada por LINQ en la columna Consulta, se identifica la primera fila como la consulta asociada al método GetBlogTitleX. Para ver la cadena de consulta completa, expanda el ancho de columna si es necesario. 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 está recuperando muchos valores de columna, quizá más de los que necesita.

Para ver qué ocurre con la aplicación en términos de uso de memoria, recopile un seguimiento usando la herramienta de Asignación de objetos .NET (para C++, use en su lugar la herramienta de Uso de memoria). 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. Como cabría esperar, parece que el método GetBlogTitleX está generando una gran cantidad de objetos. De hecho, hay más de 900 000 asignaciones de objetos.

Captura de pantalla de la vista Árbol de llamadas en la herramienta Asignación de objetos de .NET.

La mayoría de los objetos creados son cadenas, matrices de objetos e Int32. Para ver cómo se generan estos tipos, es preciso examinar el código fuente.

Optimizar código

Es el momento de echar un vistazo al código fuente de GetBlogTitleX. En la herramienta Asignación de objetos de .NET, haga clic con el botón derecho en el método y elija Ir al archivo de origen. En el código fuente de GetBlogTitleX, encontramos el siguiente código, 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 en la base de datos todos los blogs cuyo autor sea "Fred Smith". Si lo examina, 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 de las propiedades contenidas en las entradas, como el identificador de blog.

Puede realizar una pequeña investigación y encontrar algunas recomendaciones comunes sobre cómo optimizar las consultas LINQ y encontrar este código.

foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
{
  Console.WriteLine("Post: " + x);
}

En este código, ha realizado varios cambios que ayudan a optimizar la consulta:

  • Agregue la cláusula Where y elimine uno de los bucles foreach.
  • Proyecte solo la propiedad Title de la instrucción Select, que es todo lo que necesita en este ejemplo.

A continuación, vuelva a realizar las pruebas que desee con las herramientas de generación de perfiles.

Comprobar los 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 %.

Captura de pantalla del uso mejorado de la CPU en la vista Árbol de llamadas de la herramienta Uso de CPU.

Cambie a la vista Grafo de llamas, donde encontrará otra visualización de la mejora. En esta vista, GetBlogTitleX también usa una parte más pequeña de la CPU.

Captura de pantalla del uso mejorado de la CPU en la vista Grafo de llamas de la herramienta Uso de CPU.

Compruebe los resultados en el seguimiento de la herramienta Base de datos y esta consulta solo leer dos registros, en lugar de 100 000. Además, la consulta se ha simplificado mucho y elimina el elemento LEFT JOIN innecesario que se generó anteriormente.

Captura de pantalla de un menor tiempo de consulta en la herramienta Base de datos.

A continuación, vuelva a comprobar los resultados en la herramienta de Asignación de objetos de .NET y vea que GetBlogTitleX solo es responsable de 56 000 asignaciones de objetos, casi un 95 % de reducción con respecto a las 900 000 anteriores.

Captura de pantalla del menor número de asignaciones de memoria en la herramienta Asignación de objetos de .NET.

Iteración

Es posible que sea necesario realizar varias optimizaciones y puede seguir iterando con los cambios de código para ver qué cambios mejoran el rendimiento y reducen el costo de proceso.

Pasos siguientes

Las siguientes entradas de blog proporcionan más información para ayudarle a aprender a usar las herramientas de rendimiento de Visual Studio de forma eficaz.