La optimización interprocedimental ( IPO ) es un conjunto de técnicas de compilación utilizadas en la programación informática para mejorar el rendimiento de programas que contienen muchas funciones de uso frecuente de longitud corta o media. La IPO se diferencia de otras optimizaciones de compilación al analizar el programa completo en lugar de una sola función o bloque de código.
IPO busca reducir o eliminar cálculos duplicados y el uso ineficiente de la memoria, así como simplificar secuencias iterativas como los bucles. Si se produce una llamada a otra rutina dentro de un bucle, el análisis de IPO puede determinar que lo mejor es insertarla en línea . Además, IPO puede reordenar las rutinas para optimizar la distribución y la localidad de la memoria .
La optimización de la salida de código ( IPO) también puede incluir optimizaciones típicas del compilador aplicadas a todo el programa, como la eliminación de código muerto (DCE), que elimina el código que nunca se ejecuta. La IPO también busca garantizar un mejor uso de las constantes. Los compiladores modernos ofrecen la IPO como una opción en tiempo de compilación. El proceso de IPO puede ocurrir en cualquier etapa entre el código fuente legible y la generación de un programa binario ejecutable.
Para los lenguajes que se compilan archivo por archivo, una optimización de punto de entrada (IPO) eficaz en las unidades de traducción (archivos de módulo) requiere conocer los puntos de entrada del programa para poder ejecutar una optimización de programa completo ( WPO ). En muchos casos, esto se implementa como una optimización en tiempo de enlace ( LTO ), ya que el enlazador tiene acceso a todo el programa.
Análisis
El objetivo de cualquier optimización de velocidad es que el programa se ejecute lo más rápido posible; el problema es que un compilador no puede analizar correctamente un programa y determinar qué hará , y mucho menos qué pretendía el programador . En cambio, los programadores humanos parten de un objetivo claro e intentan crear un programa que lo consiga, preferiblemente sin dedicarle demasiado tiempo ni esfuerzo.
Por diversas razones, entre ellas la legibilidad, los programas suelen dividirse en varios procedimientos que gestionan algunos casos generales. Sin embargo, la generalidad de cada procedimiento puede generar un desperdicio de recursos en usos específicos. La optimización interprocedimental representa un intento de reducir este desperdicio.
Supongamos que existe un procedimiento que evalúa f(x), y que fes una función pura , y el código solicita el resultado de f(6)y luego, más tarde, f(6)de nuevo. Esta segunda evaluación es casi con seguridad innecesaria: el resultado podría haberse guardado y consultado posteriormente. Esta sencilla optimización se ve frustrada en el momento en que la implementación de f(x)se vuelve impura; es decir, su ejecución implica referencias a parámetros distintos del argumento explícito 6que ha cambiado entre las invocaciones, o efectos secundarios como imprimir algún mensaje en un registro, contar el número de evaluaciones, acumular el tiempo de CPU consumido, preparar tablas internas para facilitar las invocaciones posteriores de parámetros relacionados, etc. Perder estos efectos secundarios al no realizar una segunda evaluación puede ser aceptable, o no.
En términos más generales, además de la optimización, la segunda razón para usar procedimientos es evitar la duplicación de código que produciría los mismos resultados, o resultados casi idénticos, cada vez que se ejecute el procedimiento. Por lo tanto, un enfoque general para la optimización sería invertir este proceso: algunas o todas las invocaciones de un procedimiento determinado se reemplazan por el código correspondiente, con los parámetros sustituidos adecuadamente. El compilador intentará entonces optimizar el resultado.
WPO y LTO
La optimización de programa completo ( WPO , por sus siglas en inglés) es la optimización que realiza el compilador de un programa utilizando información sobre todos los módulos que lo componen. Normalmente, las optimizaciones se realizan módulo por módulo, durante la compilación ; pero este enfoque, si bien facilita la escritura y las pruebas y consume menos recursos durante la compilación, no permite tener certeza sobre la seguridad de ciertas optimizaciones, como la inserción agresiva de código en línea , y por lo tanto no puede realizarlas incluso si resultaran ser mejoras de eficiencia que no alteran la semántica del código objeto generado.
La optimización en tiempo de enlace ( LTO , por sus siglas en inglés) es un tipo de optimización de programas que realiza un compilador en tiempo de enlace . Esta optimización es relevante en lenguajes de programación que compilan programas archivo por archivo y luego los enlazan (como C y Fortran ), en lugar de hacerlo todo a la vez (como la compilación justo a tiempo (JIT, por sus siglas en inglés ) de Java ).
Una vez que todos los archivos se han compilado por separado en archivos objeto , tradicionalmente, un compilador enlaza (combina) los archivos objeto en un único archivo, el ejecutable . Sin embargo, en LTO, tal como lo implementan GNU Compiler Collection (GCC) y LLVM , el compilador puede volcar su representación intermedia (IR), es decir, el bytecode de GIMPLE o el bitcode de LLVM, respectivamente, de modo que todas las diferentes unidades de compilación que conformarán un único ejecutable puedan optimizarse como un solo módulo cuando finalmente se produce el enlace. Esto amplía el alcance de las optimizaciones interprocedimentales para abarcar todo el programa (o, mejor dicho, todo lo que es visible en tiempo de enlace). Con la optimización en tiempo de enlace, el compilador puede aplicar diversas formas de optimización interprocedimental a todo el programa, lo que permite un análisis más profundo, una mayor optimización y, en última instancia, un mejor rendimiento del programa.
En la práctica, LTO no siempre optimiza todo el programa: las funciones de biblioteca , especialmente los objetos compartidos enlazados dinámicamente , se excluyen intencionalmente para evitar duplicaciones excesivas y permitir actualizaciones. El enlace estático se presta naturalmente al concepto de LTO, pero solo funciona con archivos de biblioteca que contienen objetos IR en lugar de archivos objeto que solo contienen código máquina. [ 1 ] Debido a problemas de rendimiento, ni siquiera la unidad completa se usa siempre directamente: un programa podría particionarse en un LTO de estilo divide y vencerás como WHOPR de GCC. [ 2 ] Y, por supuesto, cuando el programa que se está compilando es en sí mismo una biblioteca, la optimización mantendría cada símbolo disponible externamente (exportado), sin intentar eliminarlos demasiado como parte de DCE. [ 1 ]
Una forma mucho más limitada de WPO sigue siendo posible sin LTO, como lo ejemplifica el -fwhole-programinterruptor de GCC. Este modo hace que GCC asuma que el módulo que se está compilando contiene el punto de entrada de todo el programa, de modo que cualquier otra función en él no se utilice externamente y pueda optimizarse de forma segura. Dado que solo se aplica a un único módulo, no puede abarcar realmente todo el programa. Se puede combinar con LTO en el sentido de un único módulo grande, lo cual es útil cuando el enlazador no se comunica con GCC sobre qué puntos de entrada o símbolos se están utilizando externamente. [ 1 ]
Historia
Para lenguajes procedimentales como ALGOL , el análisis y la optimización interprocedimentales parecen haber entrado en la práctica comercial a principios de la década de 1970. El compilador optimizador PL/I de IBM realizaba análisis interprocedimentales para comprender los efectos secundarios tanto de las llamadas a procedimientos como de las excepciones (convertidas, en términos de PL/I como "en condiciones") [ 3 ] y en artículos de Fran Allen . [ 4 ] [ 5 ] El trabajo en la compilación del lenguaje de programación APL era necesariamente interprocedimental. [ 6 ] [ 7 ]
Las técnicas de análisis y optimización interprocedimentales fueron objeto de investigación académica en las décadas de 1980 y 1990. Reaparecieron en el mundo de los compiladores comerciales a principios de la década de 1990 con compiladores de Convex Computer Corporation (el "Application Compiler" para el Convex C4) y de Ardent (el compilador para el Ardent Titan ). Estos compiladores demostraron que las tecnologías podían ser lo suficientemente rápidas como para ser aceptables en un compilador comercial; posteriormente, las técnicas interprocedimentales han aparecido en varios sistemas comerciales y no comerciales.
Banderas e implementación
Similar a Unix
La colección de compiladores GNU tiene inserción de funciones en línea en todos los niveles de optimización. En -O1esto solo se aplica a aquellas llamadas solo una vez ( -finline-functions-once), en -O2esta restricción se relaja ( -finline-functions). Por defecto, este es un comportamiento de un solo archivo, pero con la optimización en tiempo de enlace -fltose convierte en todo el programa. [ 1 ] La interfaz de línea de comandos de Clang es similar a la de GCC, con la excepción de que no hay -fwhole-programopción. [ 8 ]
Los archivos objeto generados por LTO contienen una representación intermedia (IR) específica del compilador que se interpreta en tiempo de enlace. Para garantizar una buena compatibilidad con las bibliotecas estáticas , los enlazadores GNU más recientes cuentan con una interfaz de "complemento de enlazador" que permite al compilador convertir los archivos objeto a código máquina cuando sea necesario. Este complemento también contribuye al funcionamiento general del proceso LTO. Como alternativa, se puede generar un objeto "LTO completo" que contenga tanto código máquina como la IR, pero esto requiere más espacio. [ 1 ]
Dado que tanto GCC como LLVM (clang) pueden generar un IR a partir de diversos lenguajes de programación, la IPO en tiempo de enlace puede ocurrir incluso entre diferentes lenguajes. Esto se demuestra con mayor frecuencia con C y C++, [ 9 ] pero LLVM también lo posibilita para Rust y todos los demás compiladores basados en LLVM. [ 10 ]
Opciones que no son LTO
GCC y Clang realizan IPO por defecto en el nivel de optimización 2. Sin embargo, el grado de optimización es limitado cuando LTO está deshabilitado, ya que IPO solo puede ocurrir dentro de un archivo objeto y las funciones no estáticas nunca pueden eliminarse. Este último problema tiene una solución sin LTO: -fwhole-programse puede usar el interruptor para asumir que solo main()es no estático, es decir, visible desde el exterior. [ 11 ]
Otra técnica que no utiliza LTO son las "secciones de funciones" ( -ffunction-sectionsen GCC y Clang). Al colocar cada función en su propia sección del archivo objeto, el enlazador puede eliminar el código muerto sin una IR eliminando las secciones sin referencia (usando la opción del enlazador --gc-sections). [ 12 ] Una opción similar está disponible para las variables, pero produce un código mucho peor.
Otro
Los compiladores Intel C/C++ permiten la optimización interprocedimental de todo el programa. El indicador para habilitar la optimización interprocedimental para un solo archivo es -ip, el indicador para habilitar la optimización interprocedimental en todos los archivos del programa es -ipo. [ 13 ] [ 14 ]
El compilador MSVC , integrado en Visual Studio, también admite la optimización interprocedimental en todo el programa. [ 15 ]
Una interfaz independiente del compilador para habilitar optimizaciones interprocedimentales de todo el programa se realiza a través de la INTERPROCEDURAL_OPTIMIZATIONpropiedad en CMake . [ 16 ]
Véase también
Referencias
- 1 2 3 4 5 "Optimizar opciones" . Usando la Colección de Compiladores GNU (GCC) .
Las optimizaciones en tiempo de enlace no requieren la presencia del programa completo para funcionar. Si el programa no requiere la exportación de ningún símbolo, es posible combinar -flto y -fwhole-program para permitir que los optimizadores interprocedimentales utilicen supuestos más agresivos que pueden conducir a mejores oportunidades de optimización. El uso de -fwhole-program no es necesario cuando el complemento del enlazador está activo (ver -fuse-linker-plugin).
- ↑ "Descripción general de LTO" . Aspectos internos de la colección de compiladores GNU (GCC) .
- ↑ Thomas C. Spillman, "Exponiendo efectos secundarios en un compilador optimizador de PL/I", en Actas de IFIPS 1971 , North-Holland Publishing Company, páginas 376-381.
- ↑ Frances E. Allen, "Análisis del flujo de datos interprocedimental", Actas de IFIPS, 1974.
- ↑ Frances E. Allen y Jack Schwartz, "Determinación de las relaciones de flujo de datos en una colección de procedimientos", Informe de investigación de IBM RC 4989, agosto de 1974.
- ↑ Philip Abrams , "Una máquina APL", Departamento de Ciencias de la Computación de la Universidad de Stanford, Informe STAN-CS-70-158, febrero de 1970.
- ↑ Terrence C. Miller, "Compilación tentativa: un diseño para un compilador APL", Tesis doctoral, Universidad de Yale, 1978.
- ↑ "Referencia de argumentos de línea de comandos de Clang" . Documentación de Clang 11 .
- ↑ Reinhart, Jonathan. "¿Puede LTO para gcc o clang optimizar en métodos de C y C++?" . Stack Overflow .
- ↑ Woerister, Michael (19 de septiembre de 2019). "Cerrando la brecha: LTO entre lenguajes Rust y C/C++" . Blog de desarrolladores de LLVM .
- ↑ "Optimizar opciones" . Usando la colección de compiladores GNU (GCC) .
- ↑ "Secciones de funciones" . elinux.org .
- ↑ "Documentación del compilador Intel 8" . Archivado del original el 21 de septiembre de 2006. Consultado el 13 de febrero de 2007 .
- ↑ Compilador Intel Visual Fortran 9.1, ediciones estándar y profesional, para Windows* - Red de software de Intel
- ↑ "/GL (Optimización de todo el programa)" . Microsoft Docs . 12 de marzo de 2019. Consultado el 26 de enero de 2020 .
- ↑ "OPTIMIZACIÓN_INTERPROCEDURAL" . Documentación de CMake 3.17.2 .
Enlaces externos
- ¿Cómo engañar a los compiladores de C/C++ para que generen código pésimo?
- Optimizaciones del compilador