En teoría del compilador , la eliminación de código muerto ( DCE , dead-code removal , dead-code stripping o dead-code strip ) es una optimización del compilador para eliminar el código muerto (código que no afecta los resultados del programa). Eliminar dicho código tiene varios beneficios: reduce el tamaño del programa , una consideración importante en algunos contextos, reduce el uso de recursos como el número de bytes a transferir [1] y permite que el programa en ejecución evite ejecutar operaciones irrelevantes , lo que reduce su tiempo de ejecución . También puede permitir optimizaciones adicionales al simplificar la estructura del programa. El código muerto incluye código que nunca se puede ejecutar ( código inalcanzable ) y código que solo afecta a las variables muertas (escritas, pero nunca leídas de nuevo), es decir, irrelevantes para el programa.
Considere el siguiente ejemplo escrito en C.
int foo ( void ) { int a = 24 ; int b = 25 ; /* Asignación a variable muerta */ int c ; c = a * 4 ; return c ; b = 24 ; /* Código inalcanzable */ return 0 ; }
Un análisis simple de los usos de los valores mostraría que el valor de b
después de la primera asignación no se utiliza dentro de foo
. Además, b
se declara como una variable local dentro de foo
, por lo que su valor no se puede utilizar fuera de foo
. Por lo tanto, la variable b
está inactiva y un optimizador puede recuperar su espacio de almacenamiento y eliminar su inicialización.
Además, debido a que la primera instrucción de retorno se ejecuta incondicionalmente y no hay ninguna etiqueta después de ella a la que pueda llegar un "goto", ninguna ruta de ejecución factible llega a la segunda asignación a b
. Por lo tanto, la asignación es inalcanzable y se puede eliminar. Si el procedimiento tuviera un flujo de control más complejo , como una etiqueta después de la instrucción de retorno y una goto
en otra parte del procedimiento, entonces podría existir una ruta de ejecución factible hacia la asignación a b
.
Además, aunque algunos cálculos se realizan en la función, sus valores no se almacenan en ubicaciones accesibles fuera del alcance de esta función. Además, dado que la función devuelve un valor estático (96), se puede simplificar al valor que devuelve (esta simplificación se denomina plegado constante ).
La mayoría de los compiladores avanzados tienen opciones para activar la eliminación de código muerto, a veces en distintos niveles. Un nivel inferior podría eliminar únicamente las instrucciones que no se pueden ejecutar. Un nivel superior también podría no reservar espacio para las variables no utilizadas. Un nivel aún más alto podría determinar las instrucciones o funciones que no sirven para nada y eliminarlas.
Un uso común de la eliminación de código muerto es como alternativa a la inclusión de código opcional a través de un preprocesador . Considere el siguiente código.
int main ( void ) { int a = 5 ; int b = 6 ; int c ; c = a * ( b / 2 ); if ( 0 ) { /* DEBUG */ printf ( "%d \n " , c ); } return c ; }
Debido a que la expresión 0 siempre se evaluará como falsa , el código dentro de la declaración if nunca se puede ejecutar y la eliminación de código inactivo lo eliminaría por completo del programa optimizado. Esta técnica es común en la depuración para activar opcionalmente bloques de código; el uso de un optimizador con eliminación de código inactivo elimina la necesidad de usar un preprocesador para realizar la misma tarea.
En la práctica, gran parte del código inactivo que encuentra un optimizador es creado por otras transformaciones en el optimizador. Por ejemplo, las técnicas clásicas para la reducción de la fuerza del operador insertan nuevos cálculos en el código y hacen que los cálculos más antiguos y costosos queden inactivos. [2] La eliminación posterior del código inactivo elimina esos cálculos y completa el efecto (sin complicar el algoritmo de reducción de la fuerza).
Históricamente, la eliminación de código muerto se realizaba utilizando información derivada del análisis del flujo de datos . [3] Un algoritmo basado en el formulario de asignación única estática (SSA) aparece en el artículo de revista original sobre el formulario SSA de Ron Cytron et al. [4] Robert Shillingsburg (también conocido como Shillner) mejoró el algoritmo y desarrolló un algoritmo complementario para eliminar operaciones de flujo de control inútiles. [5]
El código muerto normalmente se considera muerto incondicionalmente . Por lo tanto, es razonable intentar eliminar el código muerto mediante la eliminación del código muerto en el momento de la compilación .
Sin embargo, en la práctica también es común que las secciones de código representen código muerto o inalcanzable solo bajo ciertas condiciones , que pueden no conocerse en el momento de la compilación o el ensamblaje. Dichas condiciones pueden ser impuestas por diferentes entornos de ejecución (por ejemplo, diferentes versiones de un sistema operativo o diferentes conjuntos y combinaciones de controladores o servicios cargados en un entorno de destino particular), que pueden requerir diferentes conjuntos de casos especiales en el código, pero al mismo tiempo se convierten en código condicionalmente muerto para los otros casos. [6] [7] Además, el software (por ejemplo, un controlador o servicio residente) puede configurarse para incluir o excluir ciertas características según las preferencias del usuario, lo que hace que las porciones de código no utilizadas sean inútiles en un escenario particular. [6] [7] Si bien se puede desarrollar software modular para cargar bibliotecas dinámicamente solo a pedido, en la mayoría de los casos, no es posible cargar solo las rutinas relevantes de una biblioteca en particular, e incluso si esto se admitiera, una rutina aún puede incluir secciones de código que pueden considerarse código muerto en un escenario dado, pero que no podrían descartarse en el momento de la compilación.
Las técnicas utilizadas para detectar dinámicamente la demanda, identificar y resolver dependencias, eliminar dicho código muerto condicionalmente y recombinar el código restante en la carga o el tiempo de ejecución se denominan eliminación dinámica de código muerto [6] [7] [ 8 ] [9 ] [10] [11] [12] [13] [14] [15] [16] [17] [18] o eliminación dinámica de instrucciones muertas . [19]
La mayoría de los lenguajes de programación, compiladores y sistemas operativos no ofrecen, o ofrecen poco más, que la carga dinámica de bibliotecas y el enlace tardío , por lo tanto, el software que utiliza la eliminación dinámica de código muerto es muy poco común en conjunto con lenguajes compilados con anticipación o escritos en lenguaje ensamblador . [8] [12] [9] Sin embargo, las implementaciones de lenguajes que realizan compilación justo a tiempo pueden optimizar dinámicamente la eliminación de código muerto. [18] [20] [21]
Aunque con un enfoque bastante diferente, a veces también se utilizan enfoques similares para la actualización dinámica de software y la aplicación de parches en caliente .
[…] cualquiera de las […] opciones se puede excluir "permanentemente" en el momento de la instalación (también ahorrará la memoria para los extractos de código correspondientes debido a nuestra
Eliminación dinámica de código muerto
), o se puede deshabilitar o habilitar en cualquier momento posterior a través de funciones API en caso de que alguien quiera evitar que un usuario pueda reiniciar la máquina. […] estamos considerando agregar más llamadas de vaciado de caché sincrónicas […] Debido a nuestro método de eliminación dinámica de código muerto, esto no causaría ningún tipo de hinchazón cuando no sea necesario en una configuración de destino particular, ya que una llamada de vaciado de caché particular se incluiría en la imagen de tiempo de ejecución de FreeKEYB solo si el caché de disco correspondiente también está cargado o si los modificadores de la línea de comandos le dijeron a FreeKEYB que cargara el soporte correspondiente.
[…] FreeKEYB crea la imagen de ejecución del controlador en el momento de la inicialización según el tipo de máquina en la que se carga, el tipo de teclado, el diseño, el país y la página de códigos utilizados, el tipo de mouse y adaptador(es) de video instalados, los otros controladores cargados en ese sistema, el sistema operativo y el método(s) de carga y reubicación utilizados, las características individuales incluidas y las opciones de configuración especificadas en la línea de comandos. Debido a la gran cantidad de opciones y parámetros de línea de comandos admitidos […] (alrededor de cincuenta parámetros […] con múltiples configuraciones posibles), existe una gran cantidad de combinaciones de características con incontables dependencias […] lo que resulta en […] un número infinito de […] imágenes de destino diferentes. La técnica de Eliminación Dinámica de Código Muerto de FreeKEYB logra resolver […] estas […] dependencias y […] eliminar código muerto y datos […] no se limita a […] incluir o excluir un número algo limitado de módulos o subrutinas completas y arreglar algunas tablas de despacho como en la programación TSR clásica, sino que […] funciona […] a nivel de byte […] capaz de eliminar […] instrucciones individuales en medio de rutinas más grandes […] distribuidas por todo el código para manejar un caso particular o soportar una característica específica […] se utilizan herramientas especiales para analizar el código […] y crear […] tablas de arreglo […] automatizadas […] usando definiciones condicionales […] para declarar los diversos casos […] no solo opcionales en tiempo de ensamblaje sino en tiempo de inicialización […] sin la […] sobrecarga de tener al menos alguna cantidad de código muerto restante en la imagen de tiempo de ejecución […] para realizar un seguimiento de todas las dependencias entre […] estos condicionales, construir y reubicar dinámicamente la imagen de tiempo de ejecución, arreglar todas las referencias entre estas pequeñas partes binarias cambiantes y móviles […] aún permitiendo usar el pequeño modelo de estilo .COM/.SYS […] se realiza en tiempo de inicialización […] API para importar y exportar estructuras de objetos entre FreeKEYB y la aplicación que realiza la llamada […] para redimensionarlos y moverlos internamente de forma transparente […] en tiempo de ejecución […]
[…] Brandneue[s] Feature, der
dynamischen Dead-Code-Elimination
, die die jeweils notwendigen Bestandteile des Treibers erst zum Installationszeitpunkt zusammenbastelt und reloziert, so daß keine ungenutzten Code- oder Datenbereiche mehr residente bleiben (zB wenn jemand ein bestimmtes FreeKEYB- Característica no benötigt). […]
(NB. Esto representa la primera implementación conocida de eliminación de código muerto dinámica granular a nivel de bytes para software ensamblado o compilado con anticipación ).
[…] una […] característica única […] que llamamos
eliminación dinámica de código muerto
, por lo que puede en el momento de la instalación […] especificar qué componentes del controlador desea y cuáles no. Esto llega a un punto de modularización cargable dinámica y vinculación tardía que no he visto en DOS hasta ahora. Si no le gusta el protector de pantalla, las macros, la calculadora o el soporte del mouse, o <casi cualquier otra cosa>, puede especificarlo en la línea de comandos y FreeKEYB, al tiempo que tiene en cuenta todas las dependencias entre las rutinas, eliminará por completo todos los fragmentos de código, que tratan con esa característica y no son necesarios para proporcionar la funcionalidad solicitada, antes de que el controlador reubique la imagen en la ubicación de destino y se convierta en residente. Eliminar algunas funciones más pequeñas solo ahorra un par de bytes, pero excluir componentes más complejos puede ahorrar medio KB o más. También puede especificar el tamaño de las áreas de datos […]
[…] el cargador optimizará dinámicamente las áreas de datos residentes y las secciones de código a nivel de byte para eliminar cualquier redundancia del controlador según la configuración y el entorno local del hardware/software/controlador. […]
…] Dado que FreeKEYB utiliza nuestra
eliminación dinámica de código muerto
para optimizar su imagen de memoria para el entorno de destino en el momento de la carga, me gustaría añadir soporte especial a FreeKEYB para
GEOS
que se pueda controlar mediante una opción de línea de comandos, de modo que el código adicional solo se cargue cuando también se utiliza GEOS. […]
…] Estaría dispuesto a agregar ganchos especiales a FreeKEYB, nuestro controlador de teclado DOS muy avanzado, ¿mejoraría la usabilidad bajo
GEOS?
[…] Debido a nuestra nueva y sofisticada tecnología
de eliminación dinámica de código muerto
, que elimina a nivel de byte cualquier fragmento de código no utilizado en un controlador, usuario o configuración del sistema y entorno de hardware en particular cuando el instalador del controlador crea y reubica la imagen de carga de sí mismo, esto no tendría ningún impacto en la memoria para los usuarios que no son de GEOS, por lo que no hay mucho de qué preocuparse (huella de memoria, etc.) como en los controladores DOS codificados tradicionalmente. […]
[…]
La evaluación diferida
es básicamente
la eliminación dinámica de código muerto
. […]
(NB. Posiblemente el primer uso público del término eliminación dinámica de código muerto , aunque sólo conceptualmente y con un enfoque en la evaluación perezosa en lenguajes funcionales ).