Soy un principiante en lenguaje ensamblador y he notado que el código x86 emitido por los compiladores generalmente mantiene el puntero del marco incluso en modo de lanzamiento / optimizado cuando podría usar el EBP
registro para otra cosa.
Entiendo por qué el puntero del marco puede hacer que el código sea más fácil de depurar, y podría ser necesario si alloca()
se llama dentro de una función. Sin embargo, x86 tiene muy pocos registros y usar dos de ellos para mantener la ubicación del marco de la pila cuando uno sería suficiente simplemente no tiene sentido para mí. ¿Por qué omitir el puntero del marco se considera una mala idea incluso en compilaciones optimizadas / de lanzamiento?
performance
assembly
x86
dsimcha
fuente
fuente
alloca
) 3. facilidad de implementación en tiempo de ejecución: manejo de exceptoins, sandbox, GCRespuestas:
El puntero de marco es un puntero de referencia que permite a un depurador saber dónde está la variable local o un argumento con un único desplazamiento constante. Aunque el valor de ESP cambia durante el transcurso de la ejecución, EBP sigue siendo el mismo, lo que hace posible alcanzar la misma variable en el mismo desplazamiento (como el primer parámetro siempre estará en EBP + 8, mientras que los desplazamientos de ESP pueden cambiar significativamente ya que estará presionando / haciendo estallar cosas)
¿Por qué los compiladores no desechan el puntero de marco? Porque con el puntero de marco, el depurador puede averiguar dónde están las variables y los argumentos locales usando la tabla de símbolos, ya que se garantiza que estarán en un desplazamiento constante de EBP. De lo contrario, no hay una manera fácil de averiguar dónde está una variable local en cualquier punto del código.
Como mencionó Greg, también ayuda a desenrollar la pila para un depurador, ya que EBP proporciona una lista vinculada inversa de marcos de pila, lo que permite que el depurador determine el tamaño del marco de pila (variables locales + argumentos) de la función.
La mayoría de los compiladores ofrecen una opción para omitir los punteros a los marcos, aunque dificulta mucho la depuración. Esa opción nunca debe usarse globalmente, incluso en el código de lanzamiento. No sabe cuándo tendrá que depurar el bloqueo de un usuario.
fuente
-fomit-frame-pointer
. Esa configuración es la predeterminada en gcc reciente..eh_frame_hdr
sección también se usa para excepciones en tiempo de ejecución. Lo encontrará (conobjdump -h
) en la mayoría de los binarios en un sistema Linux, tiene aproximadamente 16k para/bin/bash
, frente a 572B para GNU/bin/true
, 108k paraffmpeg
. Hay una opción de gcc para deshabilitar su generación, pero es una sección de datos "normal", no una sección de depuración que sestrip
elimina por defecto. De lo contrario, no podría rastrear una función de biblioteca que no tuviera símbolos de depuración. Esa sección puede ser más grande que laspush/mov/pop
instrucciones que reemplaza, pero tiene un costo de tiempo de ejecución cercano a cero (por ejemplo, uop cache).Solo agrego mis dos centavos a las ya buenas respuestas.
Es parte de una buena arquitectura de lenguaje tener una cadena de marcos de pila. El BP apunta al marco actual, donde se almacenan las variables locales de subrutina. (Los locales tienen compensaciones negativas y los argumentos tienen compensaciones positivas).
La idea de que impide que se utilice un registro perfectamente bueno en la optimización plantea la pregunta: ¿cuándo y dónde vale la pena la optimización?
La optimización solo vale la pena en bucles estrechos que 1) no llaman a funciones, 2) donde el contador del programa pasa una fracción significativa de su tiempo, y 3) en el código que el compilador realmente verá (es decir, funciones que no son de biblioteca). Suele ser una fracción muy pequeña del código general, especialmente en sistemas grandes.
Otro código puede retorcerse y exprimirse para deshacerse de los ciclos, y simplemente no importará, porque el contador del programa prácticamente nunca está allí.
Sé que no preguntaste esto, pero en mi experiencia, el 99% de los problemas de rendimiento no tienen nada que ver con la optimización del compilador. Tienen mucho que ver con el diseño excesivo.
fuente
Depende del compilador, ciertamente. He visto código optimizado emitido por compiladores x86 que utilizan libremente el registro EBP como un registro de propósito general. (Sin embargo, no recuerdo con qué compilador noté eso).
Los compiladores también pueden optar por mantener el registro EBP para ayudar a desenrollar la pila durante el manejo de excepciones, pero nuevamente esto depende de la implementación precisa del compilador.
fuente
-fomit-frame-pointer
cuando la optimización está habilitada. (cuando la ABI lo permite). GCC, clang, ICC y MSVC lo hacen, IIRC, incluso cuando apuntan a Windows de 32 bits. Sí, mi respuesta a ¿Por qué es mejor usar el registro ebp que el esp para ubicar parámetros en la pila? muestra que incluso Windows de 32 bits puede omitir el puntero del marco. Linux x86 de 32 bits definitivamente puede y lo hace. Y, por supuesto, las ABI de 64 bits han permitido la omisión del puntero de trama desde el principio.Esto es cierto solo en el sentido de que los códigos de operación solo pueden abordar 8 registros. El procesador en sí tendrá muchos más registros que eso y utilizará el cambio de nombre de los registros, la canalización, la ejecución especulativa y otras palabras de moda del procesador para sortear ese límite. Wikipedia tiene un buen párrafo introductorio sobre lo que puede hacer un procesador x86 para superar el límite de registro: http://en.wikipedia.org/wiki/X86#Current_implementations .
fuente
El uso de marcos de pila se ha vuelto increíblemente barato en cualquier hardware, incluso remotamente moderno. Si tiene marcos de pila baratos, guardar un par de registros no es tan importante. Estoy seguro de que los marcos de pila rápidos frente a más registros fue una compensación de ingeniería, y ganaron los marcos de pila rápidos.
¿Cuánto estás ahorrando al registrarte puro? ¿Vale la pena?
fuente