Saltar al contenido

Optimizaciones de ensamblado de GCC: ¿por qué son equivalentes?

Agradeceríamos tu apoyo para compartir nuestras crónicas en referencia a las ciencias informáticas.

Solución:

Gracias, Kin3TiX, por hacer una pregunta de asm-novato que no era solo un volcado de código de un código desagradable sin comentarios, y un problema realmente simple. 🙂

Como una forma de mojarse los pies con ASM, sugeriría trabajar con funciones DIFERENTES a main. por ejemplo, solo una función que toma dos argumentos enteros y los agrega. Entonces el compilador no puede optimizarlo. Todavía puede llamarlo con constantes como argumentos, y si está en un archivo diferente de main, no se incluirá en línea, por lo que incluso puede recorrerlo con un solo paso.

Comprender lo que está sucediendo en el nivel de ASM tiene algunos beneficios cuando se compila main, pero aparte de los sistemas integrados, solo escribirás bucles internos optimizados en asm. En mi opinión, no tiene mucho sentido usar asm si no vas a optimizarlo al máximo. De lo contrario, probablemente no superará la salida del compilador de la fuente, que es mucho más fácil de leer.

Otros consejos para comprender la salida del compilador: compile con
gcc -S -fno-stack-check -fverbose-asm. Los comentarios después de cada instrucción suelen ser buenos recordatorios de para qué fue esa carga. Muy pronto degenera en un lío de temporales con nombres como D.2983, pero algo como
movq 8(%rdi), %rcx # a_1(D)->elements, a_1(D)->elements le ahorrará un viaje de ida y vuelta a la referencia ABI para ver en qué función entra arg %rdi, y qué miembro de estructura está en el desplazamiento 8.

Consulte también ¿Cómo eliminar el “ruido” de la salida del ensamblaje GCC / clang?


¿Qué hacen las líneas que van desde .cfi_startproc a call__main?

    _main:
LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5

.cfi cosas son información de desenrollado de pila para depuradores (y manejo de excepciones de C ++) para desenrollar la pila. No estará allí si mira asm desde objdump -d salida en lugar de gcc -S, o puedes usar -fno-asynchronous-unwind-tables.

El asunto de empujar %ebp y luego establecerlo en el valor del puntero de la pila en la entrada de la función configura lo que se llama un “marco de pila”. Esta es la razón por %ebp se llama puntero base. Estos insns no estarán allí si compila con -fomit-frame-pointer, que le da al código un registro adicional para trabajar. Eso está activado de forma predeterminada en -O2. (Esto es enorme para 32 bits x86, ya que le lleva de 6 a 7 reglas utilizables. (%esp todavía está ligado al ser el puntero de la pila; En teoría, es posible guardarlo temporalmente en un registro xmm o mmx y luego usarlo como otro registro GP, pero los compiladores nunca harán eso y hace que las cosas asíncronas como las señales POSIX o Windows SEH sean inutilizables, además de dificultar la depuración).

los leave instrucción antes del ret también es parte de este material de marco de pila.

Los indicadores de cuadro son en su mayoría un bagaje histórico, pero hacen que las compensaciones en el cuadro de la pila sean consistentes. Con los símbolos de depuración, puede rastrear la pila de llamadas sin problemas incluso con -fomit-frame-pointery es el predeterminado para amd64. (La ABI amd64 tiene requisitos de alineación para la pila, también es MUCHO mejor en otros aspectos. Por ejemplo, pasa argumentos en las reglas en lugar de en la pila).

    andl    $-16, %esp
    subl    $16, %esp

los and alinea la pila con un límite de 16 bytes, independientemente de lo que era antes. los sub reserva 16 bytes en la pila para esta función. (Observe cómo falta en la versión optimizada, porque optimiza cualquier necesidad de almacenamiento en memoria de cualquier variable).

    call    ___main

__main (nombre de asm = ___main) es parte de cygwin: llama a las funciones constructor / init para bibliotecas compartidas (incluida libc). En GNU / Linux, esto lo maneja _start (antes de que se alcance main) e incluso enlaces dinámicos que permiten que libc se inicialice antes que el ejecutable. _start incluso se alcanza. He leído que los ganchos del enlazador dinámico (o _start a partir de una static ejecutable) en lugar de código en mainharía ser posible bajo Cygwin, pero simplemente eligen no hacerlo de esa manera.

(Este mensaje de lista de correo antiguo indica _main es para constructores, pero ese main no debería tener que llamarlo en plataformas que admiten obtener el código de inicio para llamarlo).

    movb    $5, 15(%esp)
    movb    $10, 14(%esp)
    movsbl  15(%esp), %edx
    movsbl  14(%esp), %eax
    addl    %edx, %eax
    leave
    ret

¿Por qué la salida inicial de GCC es mucho más detallada?

Sin las optimizaciones habilitadas, gcc mapea las declaraciones de C lo más literalmente posible en asm. Hacer cualquier otra cosa llevaría más tiempo de compilación. Por lo tanto, movb es de los inicializadores para sus dos variables. El valor de retorno se calcula haciendo dos cargas (con extensión de signo, porque necesitamos convertir a int ANTES de agregar, para que coincida con la semántica del código C tal como está escrito, en lo que respecta al desbordamiento).

No puedo imaginar para qué sirven las dos operaciones de resta.

Sólo hay uno sub instrucción. Reserva espacio en la pila para las variables de la función, antes de la llamada a __main. ¿De qué otro submarino estás hablando?

¿Qué hacen .section, .ident, .def .p2align, etc., etc.?

Consulte el manual del ensamblador GNU. También disponible localmente como páginas de información: ejecutar info gas.

.ident y .def: Parece que gcc pone su sello en el archivo de objeto, para que pueda saber qué compilador / ensamblador lo produjo. No es relevante, ignórelos.

.section: determina en qué sección del objeto ELF archivan los bytes de todas las instrucciones o directivas de datos siguientes (p. ej. .byte 0x00) entrar, hasta el próximo .section directiva de ensamblador. Cualquiera code (solo lectura, compartible), data (datos de lectura / escritura inicializados, privados), o bss (segmento de almacenamiento de bloque. inicializado en cero, no ocupa ningún espacio en el archivo de objeto).

.p2align: Poder de 2 Alinear. Rellene con las instrucciones nop hasta la alineación deseada. .align 16 es lo mismo que .p2align 4. Las instrucciones de salto son más rápidas cuando el objetivo está alineado, debido a que la instrucción se recupera en fragmentos de 16B, no cruza un límite de página o simplemente no cruza un límite de línea de caché. (La alineación 32B es relevante cuando el código ya está en la caché uop de un Intel Sandybridge y posterior). Consulte los documentos de Agner Fog, por ejemplo.

El núcleo de por qué agregué este bit es para ilustrar por qué estoy confundido de que la versión de 4 líneas de este código ensamblador pueda lograr efectivamente el mismo efecto que las demás. Me parece que GCC ha agregado muchas “cosas” cuyo propósito no puedo discernir.

Ponga el código de interés en una función por sí misma. Muchas cosas son especiales sobre main.

Tienes razón en que un mov-inmediato y a ret son todo lo que se necesita para implementar la función, pero gcc aparentemente no tiene atajos para reconocer programas completos triviales y omitir mainmarco de pila de ‘s o la llamada a _main. >.

Sin embargo, buena pregunta. Como dije, simplemente ignore toda esa basura y preocúpese solo por la pequeña parte que desea optimizar.

.cfi (información de trama de llamada) las directivas se utilizan en gas (Gnu ASsembler) principalmente para depurar. Permiten que el depurador desenrolle la pila. Para deshabilitarlos, puede usar el siguiente parámetro cuando invoca el controlador de compilación -fno-asynchronous-unwind-tables.

Si desea jugar con el compilador en general, puede usar el siguiente comando de invocación del controlador de compilación -o -S -masm=intel -fno-asynchronous-unwind-tables o simplemente usa el compilador interactivo de godbolt

Sección de Reseñas y Valoraciones

Nos puedes estimular nuestra función añadiendo un comentario o dejando una valoración te lo agradecemos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *