5

Esta pregunta no es específica de C++, AFAIK ciertos tiempos de ejecución como Java RE pueden hacer una optimización guiada por perfiles sobre la marcha, eso también me interesa.¿La optimización guiada por perfil realizada por el compilador afecta especialmente a los casos no cubiertos con el conjunto de datos de generación de perfiles?

MSDN describes PGO así:

  1. instrumento que mi programa y ejecutar el programa bajo perfilador, entonces
  2. el compilador utiliza los datos recogidos por perfilador de reorganizar de forma automática ramificación y los bucles de tal manera que predicción errónea rama se reduce y lo más frecuente es que el código se coloque de forma compacta para mejorar su localidad

Ahora, obviamente, el resultado de los perfiles dependerá de un conjunto de datos utilizado.

Con la optimización y el perfil manual normal, encontraría algunos cuellos de botella y mejoraría esos cuellos de botella, y es probable que deje intactos todos los demás códigos. PGO parece mejorar a menudo ejecutar código a expensas de hacer raramente ejecutar código más lento.

Ahora, ¿qué ocurre si ese código lento se ejecuta a menudo en otro conjunto de datos que el programa verá en el mundo real? ¿Se degradará el rendimiento del programa en comparación con un programa compilado sin PGO y cuán mala será la degradación? En otras palabras, ¿PGO realmente mejora el rendimiento de mi código para el conjunto de datos de perfiles y posiblemente lo empeora para otros conjuntos de datos? ¿Hay algún ejemplo real con datos reales?

+0

Sé que suena obvio, pero, como regla general, debe intentar realizar la creación de perfiles cuando se trabaja con datos del mundo real, cuidando (al menos intentando) cubrir todos los caminos de datos posibles. – CAFxX

Respuesta

0

PGO ciertamente puede afectar el tiempo de ejecución del código que se ejecuta con menos frecuencia. Después de todo, estás modificando la ubicación de algunas funciones/bloques y eso hará que los bloques que están ahora juntos sean más compatibles con la caché.

Lo que he visto es que los equipos identifican sus escenarios de alta prioridad. Luego los ejecutan para entrenar el perfilador de optimización y medir la mejora. No desea ejecutar todos los escenarios bajo PGO porque si lo hace, es mejor que no ejecute ninguno.

Como en todo lo relacionado con el rendimiento, debe medir antes de aplicarlo. Mezcle sus escenarios más comunes para ver si mejoraron en absoluto mediante el uso de capacitación PGO. Y también mida los escenarios menos comunes para ver si retrocedieron en absoluto.

9

Descargo de responsabilidad: No he hecho más con PGO que haber leído y probado una vez con un proyecto de ejemplo para divertirme. Mucho de lo siguiente se basa en mi experiencia con las optimizaciones "no PGO" y las conjeturas fundamentadas. TL; DR a continuación.

This page enumera las optimizaciones realizadas por PGO. Deja mirada en ellos uno por uno (División por impacto):

Inlining-Por ejemplo, si existe una función A que llama con frecuencia la función B, y la función B es relativamente pequeño, entonces el perfil optimizaciones se guiada por función en línea B en función de A.

Registro de asignación-Optimización con resultados de datos de perfil en el registro mejor asignación.

especulación llamada virtual-Si una llamada virtual, u otra llamada a través de un puntero de función, con frecuencia se dirige a una determinada función, una optimización guiada por perfiles puede insertar una llamada directa ejecutado condicionalmente a la función objetivo frecuente , y la llamada directa puede estar en línea.

Aparentemente, esto mejora la predicción de si algunas optimizaciones dan o no resultado. Sin compensación directa para rutas de código no perfiladas.


bloque básico Optimización-optimización bloque básico permite ejecutado comúnmente bloques básicos que se ejecutan dentro de un marco temporal determinado a ser colocados en el mismo conjunto de páginas (localidad). Esto minimiza la cantidad de páginas utilizadas, lo que minimiza la sobrecarga de memoria.

Layout Función-En base al gráfico de llamadas y de llamadas/comportamiento del abonado llamado perfilado, las funciones que tienden a ser a lo largo de la misma ruta de ejecución se colocan en la misma sección.

Código Muerto Separación - Código que no se llama durante el perfilado se mueve a una sección especial que se añade al final de la serie de secciones. Esto efectivamente mantiene esta sección fuera de las páginas de uso frecuente.

EH Separación Código-El código EH, siendo ejecutada excepcionalmente, a menudo se puede mover a una sección separada cuando optimizaciones guiada por perfiles pueden determinar que las excepciones se producen sólo en condiciones excepcionales.

Todo esto puede reducir la ubicación de rutas de código no perfiladas. En mi experiencia, el impacto sería notorio o severo si esta ruta del código tiene un ciclo cerrado que excede el caché del código L1 (y tal vez incluso afecta a L2). Eso suena exactamente como un camino que debería haber sido incluido en un perfil PGO :)

La separación del código muerto puede tener un gran impacto, en ambos sentidos, porque puede reducir el acceso al disco.

Si confía en si las excepciones son rápidas, lo está haciendo mal.


Tamaño/velocidad Optimización - Funciones donde el programa pasa mucho tiempo puede ser optimizado para la velocidad. .

La regla de oro hoy en día es "optimizar para el tamaño por defecto, y sólo para optimizar la velocidad cuando sea necesario (y verificar que ayuda a) La razón es de nuevo caché de código - en la mayoría de los casos, el código más pequeños también es el código más rápido, debido a la caché del código. Por lo tanto, este tipo de automatiza lo que debe hacer manualmente. En comparación con una optimización de velocidad global, esto ralentizaría las rutas de código no perfiladas solo en casos muy atípicos ("código extraño" o máquina de destino con comportamiento de caché inusual).


condicional Optimización de red-Con las sondas de valor, optimizaciones guiada por perfiles pueden encontrar si un valor dado en una sentencia switch se utiliza con más frecuencia que otros valores. Este valor luego puede extraerse de la declaración de cambio. Lo mismo se puede hacer con las instrucciones if/else donde el optimizador puede ordenar el if/else para que el bloque if o else se coloque primero, dependiendo de qué bloque sea más frecuente.

Lo archivaría en "predicción mejorada" también, a menos que suministre la información PGO incorrecta.

El caso típico en que esto puede pagar mucho es la validación de parámetros/rangos de tiempo de ejecución y rutas similares que nunca deberían tomarse en una ejecución normal.

El caso de ruptura sería:

if (x > 0) DoThis() else DoThat(); 

en un bucle estrecho relevante y perfiles sólo el x> 0 caso.


memoria intrínseco-La expansión de los intrínsecos se puede decidir mejor si se puede determinar si un intrínseca se llama con frecuencia. Un intrínseco también se puede optimizar en función del tamaño del bloque de movimientos o copias.

De nuevo, en su mayoría, mejor información con una pequeña posibilidad de penalizar los datos no probados.

Ejemplo: - esto es todo una "conjetura educada", pero creo que es bastante ilustrativo para todo el tema.

Supongamos que tiene un memmove que siempre se invoca en búferes no superpuestos bien alineados con una longitud de 16 bytes.

Una posible optimización es verificar estas condiciones y usar instrucciones MOV en línea para este caso, llamando a un general memmove (manejo de alineación, solapamiento y longitud impar) solo cuando las condiciones no se cumplen.

Los beneficios pueden ser importantes en un circuito cerrado de estructuras de copia, a medida que mejora la localidad, reduce la instrucción de ruta esperada, probablemente con más posibilidades de emparejamiento/reordenamiento.

Sin embargo, la penalización es comparativamente pequeña: en el caso general sin PGO, siempre llamará al memmove completo, o nline la implementación completa de memmove. La optimización agrega algunas instrucciones (incluido un salto condicional) a algo bastante complejo, supongo que un 10% de gastos generales como máximo. En la mayoría de los casos, estos 10% estarán por debajo del ruido debido al acceso a la memoria caché.

Sin embargo, hay una muy ligera probabilidad de impacto significativo si la rama inesperada se toma con frecuencia y las instrucciones adicionales para el caso esperado junto con las instrucciones para el caso por defecto empujar un bucle estrecho de código L1 caché

Tenga en cuenta que ya se encuentra en los límites de lo que el compilador podría hacer por usted. Se puede esperar que las instrucciones adicionales sean de unos pocos bytes, en comparación con unos pocos K en el caché de código. Un optimizador estático podría tener el mismo destino dependiendo de qué tan bien pueda elevar invariantes y cuánto lo permite.


Conclusión:

  • Muchas de las optimizaciones son neutrales.
  • Algunas optimizaciones pueden tener leve impacto negativo en rutas que no están perfilados de código
  • el impacto es generalmente mucho más pequeñas que las posibles ganancias
  • En muy raras ocasiones, un pequeño impacto se puede acentuar por otros factores patológicos que contribuyen
  • Pocas optimizaciones (es decir, la disposición de secciones de código) pueden tener gran impacto, pero de nuevo las posibles ganancias signidicantly outweight que

Mi sensación de la tripa afirmaría además que

  • Un optimizador estática, en su conjunto, habría por lo menos la misma probabilidad de crear un caso patológico
  • Sería muy difícil de destruir realidad rendimiento incluso con entradas incorrectas PGO.

En ese nivel, sería mucho más miedo de PGO aplicación errores/fallos que de optimizaciones PGO fallidos.

Cuestiones relacionadas