c++ optimization jit performance

Optimización en tiempo de ejecución de lenguajes estáticos: ¿JIT para C++?



optimization performance (7)

¿Alguien utiliza trucos JIT para mejorar el rendimiento en tiempo de ejecución de lenguajes compilados estáticamente como C ++? Parece que el análisis de hotspot y la predicción de ramificaciones basadas en las observaciones realizadas durante el tiempo de ejecución podrían mejorar el rendimiento de cualquier código, pero tal vez haya alguna razón estratégica fundamental por la que hacer tales observaciones e implementar cambios durante el tiempo de ejecución solo sea posible en máquinas virtuales. Recuerdo claramente haber escuchado a los compiladores de compiladores de C ++ murmurar: "También puedes hacer eso con los programas escritos en C ++", mientras que los entusiastas de los lenguajes dinámicos hablan sobre la recopilación de estadísticas y el código de reorganización, pero mi búsqueda en Internet de pruebas que respalden esta memoria se ha revelado.


Creo que LLVM intenta hacer algo de esto. Intenta optimizar durante toda la vida útil del programa (tiempo de compilación, tiempo de enlace y tiempo de ejecución).


Hice ese tipo de optimización bastante en los últimos años. Fue para una API de representación gráfica que he implementado. Debido a que la API definió varios miles de modos de dibujo diferentes, la función de propósito general era muy lenta.

Terminé escribiendo mi propio pequeño compilador Jit para un lenguaje específico del dominio (muy cerca de asm, pero con algunas estructuras de control de alto nivel y variables locales incorporadas).

La mejora en el rendimiento que obtuve fue entre el factor 10 y 60 (dependía de la complejidad del código compilado), por lo que el trabajo adicional se pagó en grande.

En la PC no comenzaría a escribir mi propio compilador de jit, pero utilizaría LIBJIT o LLVM para la compilación de jit. No era posible en mi caso debido al hecho de que estaba trabajando en un procesador integrado no convencional que no es compatible con LIBJIT / LLVM, así que tuve que inventar el mío.


La optimización guiada por perfil es diferente a la optimización en tiempo de ejecución. La optimización aún se realiza fuera de línea, en función de la información de perfil, pero una vez que se envía el binario no hay una optimización en curso, por lo que si los patrones de uso de la fase de optimización guiada por perfil no reflejan con precisión el uso en el mundo real, los resultados serán Imperfecto, y el programa tampoco se adaptará a diferentes patrones de uso.

Quizás le interese buscar información sobre el Dynamo de HP , aunque ese sistema se centró en la traducción binaria nativa -> nativa, aunque ya que C ++ está compilado casi exclusivamente a código nativo, supongo que eso es exactamente lo que está buscando.

También es posible que desee echar un vistazo a LLVM , que es un marco de compilación y una representación intermedia que admite la compilación JIT y la optimización del tiempo de ejecución, aunque no estoy seguro de que exista algún tiempo de ejecución basado en LLVM que pueda compilar C ++ y ejecutar + tiempo de ejecución Optimízalo aún.


La respuesta es más probable: nadie hizo más que PGO para C ++ porque es probable que los beneficios no se noten.

Permítanme elaborar: los motores / tiempos de ejecución de JIT tienen tanto bendiciones como inconvenientes desde el punto de vista de sus desarrolladores: tienen más información en tiempo de ejecución pero muy poco tiempo para analizar. Algunas optimizaciones son realmente costosas y es poco probable que veas sin un gran impacto en la hora de inicio, como por ejemplo: desenrollado de bucle, auto-vectorización (que en la mayoría de los casos también se basa en desenrollado de bucle), selección de instrucciones (para usar SSE4.1 para CPU que usa SSE4.1) combinada con la programación de instrucciones y la reordenación (para usar mejores CPUs súper escalares). Este tipo de optimizaciones se combinan muy bien con el código tipo C (al que se puede acceder desde C ++).

La única arquitectura completa del compilador para hacer una compilación avanzada (que yo sepa) es la compilación de puntos de acceso de Java y arquitecturas con principios similares que utilizan la compilación en niveles (los sistemas de Java Azul , el popular motor de hoy en día JaegerMonkey JS).

Pero una de las mayores optimizaciones en tiempo de ejecución es la siguiente:

El almacenamiento en caché en línea polimórfico (lo que significa que si ejecuta el primer bucle con algunos tipos, la segunda vez, el código del bucle serán tipos especializados que eran del bucle anterior, y el JIT pondrá una protección y pondrá como ramal predeterminado el en línea) Los tipos, y en base a ello, a partir de esta forma especializada que utiliza un motor de forma SSA , aplicarán el plegado / propagación constante, las optimizaciones en línea, de eliminación de código muerto, y depende de qué tan avanzado esté el JIT , hará una mejora o una asignación de registro de la CPU menos mejorada.) Como puede observar, el JIT (hotspots) mejorará principalmente el código de bifurcación, y con la información de tiempo de ejecución obtendrá mejor que un código de C ++, pero un compilador estático, teniendo a su lado el tiempo para hacer el análisis El reordenamiento de instrucciones, para bucles simples, probablemente obtendrá un poco mejor rendimiento. Además, normalmente, el código C ++, las áreas que deben ser rápidas tienden a no ser OOP, por lo que la información de las optimizaciones JIT no traerá una mejora tan sorprendente.

Otra ventaja de los JIT es que JIT trabaja en ensamblajes cruzados, por lo que tiene más información si desea realizar la alineación.

Permítame explicarlo: supongamos que tiene una clase base A y que solo tiene una implementación: B en otro paquete / ensamblaje / gema / etc. y se carga dinámicamente.

El JIT como ve que B es la única implementación de A, puede reemplazar en todas partes en su representación interna las llamadas A con códigos B, y las llamadas de método no harán un envío (busque en vtable) sino que serán llamadas directas. Esas llamadas directas pueden estar en línea también. Por ejemplo, esta B tiene un método: getLength() que devuelve 2, todas las llamadas de getLength() pueden reducirse a la constante 2 en todas partes. Al final, un código C ++ no podrá omitir la llamada virtual de B de otra dll.

Algunas implementaciones de C ++ no admiten la optimización de más archivos .cpp (incluso hoy en día existe la marca -lto en las versiones recientes de GCC que hace esto posible). Pero si eres un desarrollador de C ++, preocupado por la velocidad, es probable que pongas todas las clases sensibles en la misma biblioteca estática o incluso en el mismo archivo, por lo que el compilador puede integrarlo muy bien, haciendo que la información adicional que JIT lo tiene por diseño , a ser proporcionado por el propio desarrollador, por lo que no hay pérdida de rendimiento.


Microsoft Visual Studio llama a esto " optimización guiada por perfil "; Puede aprender más sobre esto en MSDN . Básicamente, ejecuta el programa un montón de veces con un generador de perfiles adjunto para registrar sus puntos de acceso y otras características de rendimiento, y luego puede introducir la salida del perfilador en el compilador para obtener las optimizaciones adecuadas.


Pregunta razonable - pero con una premisa dudosa.

Como en la respuesta de Nils, a veces "optimización" significa "optimización de bajo nivel", lo cual es un buen tema en sí mismo.

Sin embargo, se basa en el concepto de un "punto caliente", que no tiene nada que ver con la relevancia que se le da comúnmente.

Definición: un punto caliente es una pequeña región de código donde el contador de un programa del proceso pasa un gran porcentaje de su tiempo.

Si hay un punto caliente, como un ciclo interno estrecho que ocupa mucho tiempo, vale la pena intentar optimizarlo en el nivel bajo, si está en un código que usted controla (es decir, no en una biblioteca de terceros).

Ahora suponga que el bucle interno contiene una llamada a una función, cualquier función. Ahora no es probable que se encuentre el contador del programa allí, porque es más probable que esté en la función. Entonces, si bien el código puede ser un desperdicio, ya no es un punto caliente.

Hay muchas formas comunes de hacer que el software sea lento, de los cuales los puntos calientes son uno. Sin embargo, según mi experiencia, ese es el único que la mayoría de los programadores conocen, y el único al que se aplica la optimización de bajo nivel.

Mira esto.


Visual Studio tiene una opción para hacer perfiles de tiempo de ejecución que luego se pueden utilizar para la optimización del código.

"Optimización guiada de perfil"