¿Por qué clang produce asm ineficiente con -O0 (para esta simple suma de punto flotante)?

3 minutos de lectura

avatar de usuario
Stefano Borini

Estoy desmontando este código en llvm clang Apple LLVM versión 8.0.0 (clang-800.0.42.1):

int main() {
    float a=0.151234;
    float b=0.2;
    float c=a+b;
    printf("%f", c);
}

Compilé sin especificaciones -O, pero también probé con -O0 (da lo mismo) y -O2 (en realidad calcula el valor y lo almacena precalculado)

El desmontaje resultante es el siguiente (he quitado las partes que no son relevantes)

->  0x100000f30 <+0>:  pushq  %rbp
    0x100000f31 <+1>:  movq   %rsp, %rbp
    0x100000f34 <+4>:  subq   $0x10, %rsp
    0x100000f38 <+8>:  leaq   0x6d(%rip), %rdi       
    0x100000f3f <+15>: movss  0x5d(%rip), %xmm0           
    0x100000f47 <+23>: movss  0x59(%rip), %xmm1        
    0x100000f4f <+31>: movss  %xmm1, -0x4(%rbp)  
    0x100000f54 <+36>: movss  %xmm0, -0x8(%rbp)
    0x100000f59 <+41>: movss  -0x4(%rbp), %xmm0         
    0x100000f5e <+46>: addss  -0x8(%rbp), %xmm0
    0x100000f63 <+51>: movss  %xmm0, -0xc(%rbp)
    ...

Aparentemente está haciendo lo siguiente:

  1. cargando los dos flotadores en los registros xmm0 y xmm1
  2. ponerlos en la pila
  3. cargue un valor (no el que xmm0 tenía antes) de la pila a xmm0
  4. realizar la suma.
  5. almacenar el resultado de nuevo en la pila.

Lo encuentro ineficiente porque:

  1. Todo se puede hacer en el registro. No estoy usando a y b más tarde, por lo que podría omitir cualquier operación que involucre la pila.
  2. incluso si quisiera usar la pila, podría ahorrarse recargar xmm0 de la pila si hiciera la operación con un orden diferente.

Dado que el compilador siempre tiene la razón, ¿por qué eligió esta estrategia?

  • Porque no habilitaste las optimizaciones y esta es la forma más sencilla de hacerlo.

    – sepp2k

    18 de noviembre de 2018 a las 23:18

  • Aunque la respuesta básica es simple, gracias por escribir esta pregunta bien formateada. Hay algunas cosas interesantes que decir, y este parece un buen lugar para poner una respuesta canónica que he repetido a menudo como parte de otras respuestas. Ahora puedo enlazar a esto como una opción para -O0 siendo una mala elección para mirar ASM generado por el compilador, y exactamente qué -O0 implica para el asm.

    – Peter Cordes

    19 de noviembre de 2018 a las 0:49


  • no intente predecir el tiempo de ejecución mirando el código asm/c, la caja negra EXTREMADAMENTE compleja de la CPU moderna, si no es un experto, es fácil que pueda estar equivocado. CPU ejecutando instrucciones fuera de servicio y con diferente velocidad, canalización, dependencia de datos, superescalado: todas estas cosas podrían ejecutar un programa ficticio más largo más rápido que más corto y obvio. Esa es la regla general, siempre ejecute, no mire el código.

    – Yura

    22 oct 2021 a las 11:49

  • Tenga en cuenta que al menos clang en realidad comienza con cada variable que tiene memoria en la pila asignada para ella. Uno de los primeros pases de optimización (que supongo que se omite para -O0) los convierte en un montón de variables SSA si es posible. Entonces, al menos en clang, no hay “antioptimización”, las optimizaciones normales simplemente están desactivadas.

    – fuz

    25 de enero de 2019 a las 13:31

¿Ha sido útil esta solución?