2011-11-02 10 views
5

Cuando diassembled mi programa, vi que estaba usando gcc JMP para la segunda pthread_wait_barrier llamar cuando se compila con -O3. ¿Por que es esto entonces?¿Por qué es gcc usando JMP para llamar a una función en la versión optimizada

¿Qué ventaja obtiene al usar jmp en lugar de llamada. ¿Qué trucos está jugando el compilador aquí? Supongo que está realizando una optimización de llamadas de cola aquí.

Por cierto, estoy usando enlaces estáticos aquí.

__attribute__ ((noinline)) void my_pthread_barrier_wait( 
    volatile int tid, pthread_barrier_t *pbar) 
{ 
    pthread_barrier_wait(pbar); 
    if (tid == 0) 
    { 
     if (!rollbacked) 
     { 
      take_checkpoint_or_rollback(++iter == 4); 
     } 
    } 
    //getcontext(&context[tid]); 
    SETJMP(tid); 
    asm("addr2jmp:"); 
    pthread_barrier_wait(pbar); 
    // My suspicion was right, gcc was performing tail call optimization, 
    // which was messing up with my SETJMP/LONGJMP implementation, so here I 
    // put a dummy function to avoid that. 
    dummy_var = dummy_func(); 
} 
+1

Muéstranos el código fuente. –

+1

jmp simplemente transfiere la ejecución a una nueva ubicación. call empuja cosas a la pila y, por lo tanto, es un poco más caro de usar. –

+1

Parece que realiza una optimización de llamadas de cola. – MetallicPriest

Respuesta

12

Como no se presenta un ejemplo, sólo puedo adivinar: la función llamada tiene el mismo tipo de retorno como la que llamar, y esto funciona como

return func2(...) 

o no tiene ningún tipo de devolución (void).

En este caso, "nosotros" dejamos "nuestra" dirección de retorno en la pila, dejando que "ellos" la usen para regresar a "nuestro" llamante.

6

Quizás fue una llamada recursiva de cola. GCC tiene algún pase haciendo la optimización recursiva de cola.

¿Pero por qué debería molestarse? Si la función llamada es una función extern, entonces es pública, y GCC debe llamarla siguiendo las convenciones ABI (lo que significa que sigue la convención de llamadas).

No debería importar si la función fue llamada por un jmp.

y también podría ser una llamada a una función de biblioteca dinámica (es decir, con el PLT para dynamic linking)

2

JMP tiene menos sobrecarga que llamada. JMP simplemente salta, llamada empuja un poco de materia en la pila y salta

+0

-1 para una respuesta incompleta. También sé que jmp tiene menos sobrecarga. La pregunta era cómo gcc usa jmp para realizar la misma funcionalidad que llamar en la versión optimizada. – MetallicPriest

+2

Lo que mencionas en tu comentario no está claramente indicado en la pregunta, tal vez deberías editarlo. El único enunciado de pregunta que no respondí fue "¿Qué trucos está jugando el compilador aquí?", Que no está claro y está roto. – TJD

+0

JMP tiene menos sobrecarga, seguro. Ahora la función a la que salta en lugar de llamar tiene que regresar en algún momento. Por lo tanto, la dirección de retorno debe colocarse en la pila antes del JMP o, al final de la función, debe llamar a otro JMP para regresar al lugar de donde proviene, lo cual, en general, termina siendo bastante similar. Puede guardar un par de ciclos con un JMP doble ya que no hay manipulación de pila, pero tendría que almacenar la dirección de devolución en un registro o algo así. –

2

Supongo que se trata de una llamada final, lo que significa que la función actual devuelve el resultado de la función llamada sin modificar, o (para una función que devuelve vacía) devuelve inmediatamente después de la llamada a la función. En cualquier caso, no es necesario usar call.

La instrucción call realiza dos funciones. Primero, empuja la dirección de la instrucción después de la llamada a la pila como una dirección de retorno. Luego salta al destino de la llamada. ret saca la dirección de retorno de la pila y salta a esa ubicación.

Como la función de llamada devuelve el resultado de la función llamada, no hay ninguna razón para que la operación regrese después de que la función llamada retorna. Por lo tanto, siempre que sea posible y si el nivel de optimización lo permite, GCC destruirá su marco de pila antes de la llamada de función, de modo que la parte superior de la pila contenga la dirección de retorno para la función que lo llamó, y luego simplemente salte a la función llamada. El resultado es que, cuando la función llamada vuelve, vuelve directamente a la primera función en lugar de a la función de llamada.

-1

Nunca lo sabrá, pero una de las razones probables es "caché" (entre otras razones, como la ya mencionada optimización de llamadas de cola).

Inlining puede hacer que el código sea más rápido y puede hacer que el código sea más lento, porque más código significa que habrá menos en la caché L1 a la vez.

Un JMP permite al compilador reutilizar la misma pieza de código a bajo costo o sin costo alguno. Los procesadores modernos están profundamente canalizados, y las tuberías pasan por encima de un JMP sin problemas (¡no hay posibilidad de error de predicción aquí!). En el caso promedio, costará tan poco como 1-2 ciclos, en el mejor de los casos, cero ciclos, porque la CPU tendría que esperar de todas maneras una instrucción anterior para retirarse. Obviamente, esto depende totalmente del código individual respectivo.
El compilador podría, en principio, incluso hacer eso con varias funciones que tienen partes comunes.

Cuestiones relacionadas