Compartir por


Tutorial: Solución de problemas de inserción de funciones en tiempo de compilación

Use Build Insights Functions vista para solucionar el impacto de la inserción de funciones en el tiempo de compilación en los proyectos de C++.

Requisitos previos

  • Visual Studio 2022 17.8 o posterior.
  • La información de compilación de C++ está habilitada de forma predeterminada si instala la carga de trabajo Desarrollo de escritorio con C++ o la carga de trabajo Desarrollo de juegos con C++.

Captura de pantalla del Instalador de Visual Studio con la carga de trabajo Desarrollo de escritorio con C++ seleccionada.

Se muestra la lista de componentes instalados. C++ Build Insights está resaltado y está seleccionado, lo que significa que está instalado.

Captura de pantalla del Instalador de Visual Studio con la carga de trabajo Desarrollo de juegos con C++ seleccionada.

Se muestra la lista de componentes instalados. Build Insights para C++ está resaltado y está seleccionado, lo que significa que está instalado.

Información general

Build Insights, ahora integrado en Visual Studio, le ayuda a optimizar los tiempos de compilación, especialmente para proyectos grandes como juegos AAA. Build Insights proporciona análisis como la vista Functions, que ayuda a diagnosticar la generación de código costosa durante el tiempo de compilación. Muestra el tiempo necesario para generar código para cada función y muestra el impacto de __forceinline.

La directiva __forceinline indica al compilador que inserte una función independientemente de su tamaño o complejidad. La inserción de una función puede mejorar el rendimiento en tiempo de ejecución al reducir la sobrecarga de llamar a la función. El inconveniente es que puede aumentar el tamaño del archivo binario e afectar a los tiempos de compilación.

Para las compilaciones optimizadas, el tiempo dedicado a generar código contribuye significativamente al tiempo de compilación total. En general, la optimización de funciones de C++ se produce rápidamente. En casos excepcionales, algunas funciones pueden ser lo suficientemente grandes y complejas como para presionar al optimizador y ralentizar notablemente las compilaciones.

En este artículo, aprenderá a usar la vista Build Insights Functions para buscar cuellos de botella de inserción en la compilación.

Establecer las opciones de compilación

Para medir los resultados de __forceinline, use una compilación de Release porque las compilaciones de depuración no se __forceinline alinean, ya que las compilaciones de depuración usan el modificador del compilador de /Ob0, lo que deshabilita esa optimización. Establezca la compilación para Release y x64:

  1. En la lista desplegable Configuraciones de soluciones, elija Release.
  2. En la lista desplegable Plataformas de solución, elija x64.

Captura de pantalla de la lista desplegable Configuración de la solución establecida en Versión y la lista desplegable Plataforma de soluciones establecida en x64.

Establezca el nivel de optimización en optimizaciones máximas:

  1. En el Explorador de soluciones, haga clic con el botón derecho en el nombre del proyecto y seleccione Propiedades.

  2. En las propiedades del proyecto, vaya a C/C++>Optimización.

  3. Establezca la lista desplegable Optimización en Optimización máxima (velocidad de favor) (/O2).

    Captura de pantalla del cuadro de diálogo de páginas de propiedades del proyecto. Los valores están abiertos a Propiedades de configuración > Optimización de > C/C++ >. La lista desplegable Optimización se establece en Optimización máxima (Velocidad de favor) (/O2).

  4. Haga clic en Aceptar para cerrar el cuadro de diálogo.

Ejecución de Build Insights

En un proyecto de su elección y con las opciones de compilación Release establecidas en la sección anterior, ejecute Build Insights eligiendo en el menú principal Build>Run Build Insights on Selection>Rebuild. También puede hacer clic con el botón derecho en un proyecto en el Explorador de soluciones y elegir Ejecutar Información de compilación>Recompilar. Elija Volver a compilar en lugar de Compilar para medir el tiempo de compilación de todo el proyecto y no solo para unos pocos archivos que puedan estar incorrectos ahora mismo.

Captura de pantalla del menú principal con la opción Ejecutar Build Insights en la selección > Volver a compilar seleccionada.

Cuando finaliza la compilación, se abre un archivo registro de seguimiento de eventos (ETL). Se guarda en la carpeta a la que apunta la variable de entorno de Windows TEMP. El nombre generado se basa en el tiempo de recopilación.

Vista de función

En la ventana del archivo ETL, elija la pestaña Functions. Muestra las funciones compiladas y el tiempo que tardó en generar el código para cada función. Si la cantidad de código generado para una función es insignificante, no aparecerá en la lista para evitar degradar el rendimiento de la recopilación de eventos de compilación.

Captura de pantalla del archivo de vista Funciones de Build Insights.

En la columna Nombre de función, performPhysicsCalculations() está resaltado y marcado con un icono de fuego.:::

La columna Time [sec, %] muestra cuánto tiempo se tarda en compilar cada función en tiempo de responsabilidad del reloj de pared (WCTR). Esta métrica distribuye el tiempo del reloj entre las funciones en función del uso de subprocesos del compilador paralelo. Por ejemplo, si dos subprocesos diferentes compilan dos funciones diferentes simultáneamente dentro de un período de un segundo, cada función WCTR se registra como 0,5 segundos. Esto refleja el recurso compartido proporcional de cada función del tiempo total de compilación, teniendo en cuenta los recursos consumidos durante la ejecución en paralelo. Por lo tanto, WCTR proporciona una mejor medida del impacto que tiene cada función en el tiempo de compilación general en entornos donde se producen simultáneamente varias actividades de compilación.

La columna Forceinline Size muestra aproximadamente cuántas instrucciones se generaron para la función. Haga clic en el botón de contenido adicional antes del nombre de la función para ver las funciones insertadas individuales que se expandieron en esa función, aproximadamente cuántas instrucciones se generaron para cada una.

Puede ordenar la lista haciendo clic en la columna Hora para ver qué funciones tardan más tiempo en compilarse. Un icono "fire" indica que el costo de generar esa función es alta y vale la pena investigar. El uso excesivo de funciones de __forceinline puede ralentizar considerablemente la compilación.

Puede buscar una función específica mediante el cuadro Funciones de filtro. Si el tiempo de generación de código de una función es demasiado pequeño, no aparece en la Vista Functions.

Mejora del tiempo de compilación ajustando la inserción de funciones

En este ejemplo, la función performPhysicsCalculations está tardando más tiempo en compilarse.

Captura de pantalla de la vista Functions de Build Insights.

En la columna Nombre de función, performPhysicsCalculations() está resaltado y marcado con un icono de desencadenador.

Para investigar más, seleccione el botón de contenido adicional antes de esa función y, a continuación, ordene la Tamaño de fuerza columna de mayor a menor, vemos los mayores colaboradores del problema.

Captura de pantalla de la vista Build Insights Functions con una función expandida.

performPhysicsCalculations() se expande y muestra una larga lista de funciones insertadas dentro de ella. Hay varias instancias de funciones como complexOperation(), recursiveHelper() y sin() mostradas. La columna Forceinline Size muestra que complexOperation() es la función insertada más grande en 315 instrucciones. recursiveHelper() tiene 119 instrucciones. Sin() tiene 75 instrucciones, pero hay muchas más instancias de ella que las otras funciones.

Hay algunas funciones insertadas más grandes, como Vector2D<float>::complexOperation() y Vector2D<float>::recursiveHelper() que contribuyen al problema. Pero hay muchas más instancias (no todas las que se muestran aquí) de Vector2d<float>::sin(float), Vector2d<float>::cos(float), Vector2D<float>::power(float,int), y Vector2D<float>::factorial(int). Al agregarlas, el número total de instrucciones generadas supera rápidamente las pocas funciones generadas más grandes.

Al examinar esas funciones en el código fuente, vemos que el tiempo de ejecución se va a dedicar dentro de bucles. Por ejemplo, este es el código de factorial():

static __forceinline T factorial(int n)
{
    T result = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < i; ++j) {
            result *= (i - j) / (T)(j + 1);
        }
    }
    return result;
}

Quizás el costo general de llamar a esta función es insignificante en comparación con el costo de la propia función. La creación de una función en línea es más beneficiosa cuando se tarda en llamar a la función (insertar argumentos en la pila, saltar a la función, extraer argumentos devueltos y devolver de la función) es aproximadamente similar al tiempo que se tarda en ejecutar la función y cuando se llama a la función mucho. Cuando ese no es el caso, puede haber devoluciones de disminución al hacerlo insertado. Podemos intentar quitar la directiva __forceinline de ella para ver si ayuda al tiempo de compilación. El código de power, sin() y cos() es similar en que el código consta de un bucle que se ejecutará muchas veces. También podemos intentar quitar la directiva __forceinline de esas funciones.

Vuelva a ejecutar Build Insights en el menú principal eligiendo Build>Run Build Insights on Selection>Rebuild. También puede hacer clic con el botón derecho en un proyecto en el Explorador de soluciones y elegir Ejecutar Información de compilación>Recompilar. Elegimos Recompilar en lugar de Compilar para medir el tiempo de compilación de todo el proyecto, como antes, y no solo para los pocos archivos ahora mismo.

El tiempo de compilación va de 25,181 segundos a 13,376 segundos y la función performPhysicsCalculations ya no aparece en la vista Functionsporque no contribuye lo suficiente al tiempo de compilación que se va a contar.

Captura de pantalla del archivo de encabezado de vector 2D.

En la columna Nombre de función, performPhysicsCalculations() está resaltado y marcado con un icono de fuego.:::

El tiempo de la sesión de diagnóstico es el tiempo general que tardó la compilación más cualquier sobrecarga para recopilar los datos de Build Insights.

El siguiente paso sería generar perfiles de la aplicación para ver si el rendimiento de la aplicación se ve afectado negativamente por el cambio. Si es así, podemos agregar __forceinline de forma selectiva según sea necesario.

Haga doble clic, haga clic con el botón derecho o presione Entrar mientras se encuentra en un archivo de Functions vista para abrir el código fuente de ese archivo.

Captura de pantalla de un clic derecho en un archivo en la vista Functions. La opción de menú Ir al archivo de origen está resaltada.

Sugerencias

  • Puede ir a Archivo>Guardar como para guardar el archivo ETL en una ubicación más permanente y mantener un registro del tiempo de compilación. A continuación, puede compararlo con compilaciones futuras para ver si los cambios mejoran el tiempo de compilación.
  • Si cierra accidentalmente la ventana de Build Insights, vuelva a abrirla buscando el archivo <dateandtime>.etl en la carpeta temporal. La variable de entorno de Windows TEMP proporciona la ruta de acceso de la carpeta de archivos temporales.
  • Para profundizar en los datos de Build Insights con Windows Performance Analyzer (WPA), haga clic en el botón Abrir en WPA en la parte inferior derecha de la ventana ETL.
  • Arrastre las columnas para cambiar el orden de las columnas. Por ejemplo, puede que prefiera mover la columna Hora para que sea la primera columna. Para ocultar columnas, haga clic con el botón derecho en el encabezado de columna y deseleccione las columnas que no desea ver.
  • La vista Functions proporciona un cuadro de filtro para encontrar una función que le interese. Realiza coincidencias parciales en el nombre que proporcione.
  • Si olvida cómo interpretar lo que Functions vista está intentando mostrar, mantenga el puntero sobre la pestaña para ver una información sobre herramientas que describe la vista. Si mantiene el puntero sobre la pestaña Functions, la información sobre herramientas indica: "Ver que muestra estadísticas de funciones en las que los nodos secundarios están insertados por fuerza".

Solución de problemas

  • Si no aparece la ventana de Build Insights, realice una recompilación en lugar de una compilación. La ventana Build Insights no aparece si realmente no se compila nada; que puede ser el caso si no se ha cambiado ningún archivo desde la última compilación.
  • Si la vista Functions no muestra ninguna función, es posible que no esté compilando con la configuración de optimización correcta. Asegúrese de que va a compilar Release con optimizaciones completas, como se describe en Establecer opciones de compilación. Además, si el tiempo de generación de código de una función es demasiado pequeño, no aparece en la lista.

Consulte también

Funciones insertadas (C++)
Compilaciones de C++ más rápidas y simplificadas: una nueva métrica de tiempo
Vídeo de Build Insights en Visual Studio: Pure Virtual C++ 2023
Solución de problemas del impacto del archivo de encabezado en el tiempo de compilación
Vista funciones para Build Insights en Visual Studio 2022 17.8
Tutorial: vcperf y Windows Performance Analyzer
Mejora del tiempo de generación de código con C++ Build Insights