6
votes

Pourquoi GCC utilise-t-il JMP pour appeler une fonction dans la version optimisée?

Quand j'ai diassé mon programme, j'ai vu que GCC utilisait JMP Strong> pour le second pthread_wait_barrier appel fort> lorsqu'il est compilé avec -O3. Pourquoi est-ce tellement?

Quel avantage obtient-il en utilisant JMP fort> au lieu de appel fort>. Quelles astuces le compilateur joue ici? Je suppose que son optimisation des appels de queue d'exécution ici. P>

Au fait, j'utilise ici la liaison statique ici. P>

__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();
}


4 commentaires

Montrez-nous le code source.


JMP transfère simplement l'exécution vers un nouvel emplacement. L'appel pousse des trucs sur la pile et est donc légèrement plus coûteux à utiliser.


On dirait que son optimisation d'appel de la queue exécutée.


Pouvez-vous ajouter le code de l'assembleur?


5 Réponses :


6
votes

C'était peut-être un appel de la queue récursive. GCC a un peu de passe faire une optimisation récursive de la queue.

Mais pourquoi devriez-vous déranger? Si la fonction appelée est une fonction externe , il est public, et CGC doit l'appeler après les conventions ABI (ce qui signifie qu'il suit la convention appelante).

Vous ne devriez pas vous soucier de si la fonction a été appelée par un JMP.

Et il peut également être un appel à une fonction de bibliothèque dynamique (c'est-à-dire avec la PLT pour lien dynamique )


0 commentaires

2
votes

JMP a moins de frais généraux que l'appel. JMP vient de sauter, appel appelle des trucs sur la pile et les sauts


3 commentaires

-1 pour une réponse incomplète. Je sais aussi que JMP a moins de frais généraux. La question était de savoir comment GCC utilise JMP pour effectuer la même fonctionnalité que celle-ci dans la version optimisée.


Ce que vous mentionnez dans votre commentaire n'est pas clairement indiqué dans la question, vous devriez peut-être le modifier. La seule déclaration de question que je n'ai pas répondue était "Quelles astuces le compilateur joue ici?", Qui n'est pas clair et brisé l'anglais.


JMP a moins de frais généraux, bien sûr. Maintenant, la fonction que vous sautez plutôt que d'appeler doit revenir à un moment donné. Par conséquent, l'adresse de retour doit être poussée sur la pile avant le JMP ou à la fin de la fonction que vous devez appeler un autre JMP pour revenir à l'endroit où vous venez, ce qui finit par tous étant à peu près similaire. Vous pouvez économiser quelques cycles avec un double JMP car il n'y a pas de manipulation de pile, mais vous devrez stocker l'adresse de retour dans un registre ou quelque chose.



12
votes

Comme vous n'exprimez pas d'exemple, je ne peux que deviner: la fonction appelée a le même type de retour que l'appelant, et cela fonctionne comme xxx

ou n'a pas de type de retour du tout ( void ).

Dans ce cas, "Nous" laissez "notre" adresse de retour sur la pile, laissant-la à "les" pour l'utiliser pour revenir à " Notre "appelant.


0 commentaires

2
votes

Je suppose que ceci est un appel de la queue, ce qui signifie que la fonction actuelle renvoie le résultat de la fonction appelée non modifiée ou (pour une fonction qui renvoie le vide) renvoie immédiatement après l'appel de la fonction. Dans les deux cas, il n'est pas nécessaire d'utiliser appel .

L'instruction appel effectue deux fonctions. Premièrement, il pousse l'adresse de l'instruction après l'appel sur la pile en tant qu'adresse de retour. Ensuite, il saute à la destination de l'appel. ret apparaît l'adresse de retour éteint de la pile et saute à cet emplacement.

Étant donné que la fonction d'appel renvoie le résultat de la fonction appelée, il n'y a aucune raison d'être opérationnelle à y retourner après la retours de la fonction appelée. Par conséquent, chaque fois que possible et si le niveau d'optimisation le permet, GCC détruira son cadre de pile avant l'appel de la fonction, de sorte que le haut de la pile contienne l'adresse de retour de la fonction qui l'appelait, puis passe simplement à la fonction appelée. Le résultat est que, lorsque la fonction appelée renvoie, elle revient directement à la première fonction au lieu de la fonction d'appel.


0 commentaires

-1
votes

Vous ne saurez jamais, mais l'une des raisons probables est "cache" (parmi d'autres raisons telles que l'optimisation des appels de queue déjà mentionnée).

L'inlinge peut rendre le code plus rapide et peut rendre le code plus lent, car davantage de code signifie moins de celui-ci dans le cache L1 à la fois.

Un JMP permet au compilateur de réutiliser le même code à peu ou pas de coût. Les processeurs modernes sont profondément pipelinés et les pipelines passent sur un JMP sans problèmes (il n'y a aucune possibilité de mauvaise répugnance ici!). Dans le cas moyen, cela coûtera aussi peu que 1-2 cycles, dans les meilleurs cas zéro cycles, car la CPU devrait attendre sur une instruction antérieure pour la retraite de toute façon. Cela dépend évidemment totalement sur le code individuel respectif.
Le compilateur pourrait en principe le faire avec plusieurs fonctions ayant des parties communes.


0 commentaires