es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Clang genera una salida extraña cuando se divide dos enteros.

He escrito el siguiente código muy simple con el que estoy experimentando en el explorador de compiladores de Godbolt:

#include <cstdint>

uint64_t func(uint64_t num, uint64_t den)
{
    return num / den;
}

GCC produce la siguiente salida, que es la que esperaría:

func(unsigned long, unsigned long):
        mov     rax, rdi
        xor     edx, edx
        div     rsi
        ret

Sin embargo, Clang 13.0.0 produce lo siguiente, involucrando desplazamientos y un salto incluso:

func(unsigned long, unsigned long):                              # @func(unsigned long, unsigned long)
        mov     rax, rdi
        mov     rcx, rdi
        or      rcx, rsi
        shr     rcx, 32
        je      .LBB0_1
        xor     edx, edx
        div     rsi
        ret
.LBB0_1:
        xor     edx, edx
        div     esi
        ret

Cuando se utiliza uint32_t, la salida de clang es nuevamente “simple” y lo que esperaría.

Parece que se trata de alguna forma de optimización, ya que clang 10.0.1 produce la misma salida que GCC, sin embargo, no puedo entender qué está sucediendo. ¿Por qué clang produce este ensamblado más largo?

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    La diferencia en la salida de ensamblaje entre GCC y clang para el código proporcionado con uint64_t se debe a la forma en que manejan la operación de división. GCC utiliza directamente la instrucción div, que toma el dividendo en rax y el divisor en rsi, y realiza la división sin signo, almacenando el cociente en rax y el resto en rdx.

    Por otro lado, parece que clang optimiza la operación de división comprobando si los 32 bits superiores del dividendo y del divisor son ambos cero, y si no, utiliza un algoritmo más complejo que involucra desplazamientos y un salto condicional. Esta optimización puede proporcionar un mejor rendimiento en ciertos casos, pero también puede resultar en un código de ensamblaje más largo.

    Cabe señalar que el comportamiento del código con uint32_t es diferente porque la división entre enteros de 32 bits se puede hacer de manera más eficiente utilizando la instrucción idiv, que realiza la operación de división con signo y almacena el cociente y el resto en eax y edx respectivamente. Por lo tanto, tanto GCC como clang pueden utilizar directamente la instrucción idiv para la división de uint32_t, lo que resulta en una salida de ensamblaje más simple.

    En general, si bien la optimización de clang puede mejorar el rendimiento en ciertos escenarios, es importante tener en cuenta el posible compromiso con un código de ensamblaje más largo y la necesidad de considerar cuidadosamente las implicaciones de rendimiento de diferentes optimizaciones de código.

Comments are closed.