IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Performances des exceptions C++ [Tutoriel]


Sujet :

C++

  1. #1
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    26 827
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 26 827
    Points : 218 289
    Points
    218 289
    Billets dans le blog
    117
    Par défaut Performances des exceptions C++
    Bonjour à tous,

    Je vous présente une nouvelle traduction de l'article de Vlad Lazarenko discutant des performances des exceptions en C++. Souvent, lorsque l'on écrit du code, on se demande l'impact des exceptions sur les performances de notre programme. Cet article apporte donc des éléments de réponse détaillés à cette question.

    Performances des exceptions C++

    Avez-vous évité d'utiliser des exceptions pour préserver les performances ?
    Dans quel cas avez vous pris cette décision ?
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  2. #2
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Il me semble avoir lu l'article auparavant mais le liens dans la page française n'a pas l'air de fonctionner (http://lazarenko.me/tips-and-tricks/...nd-performance) - mais peut être qu'il y a un problem d'ici, je suis au Japon.

    Sinon, comme quasiment tout le monde dans les discussions, il ignore complètement le cas ou on desactive les exceptions et qu'on ne fais aucune verification d'erreur (on compte sur des tests massifs avant publication).

  3. #3
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    L'exemple du divisé est un peu bancale. Si un precondition est violé, c'est plutot une assertion qui devrait etre levée.

  4. #4
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Bonsoir,

    tout d'abord merci pour la traduction!

    Dans le billet, l'auteur évoque une gestion sans coût des exceptions mais ne semble pas la décrire. De quoi s'agit-il?

    @Joel F : je n'ai pas compris pourquoi une assertion devrait être levée dans le cas particulier du divide. Cela ne va-t-il pas interrompre prématurément le déroulement du programme si on fait cela?

  5. #5
    Membre éclairé
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Points : 879
    Points
    879
    Par défaut
    Citation Envoyé par Aleph69 Voir le message
    Bonsoir,

    tout d'abord merci pour la traduction!

    Dans le billet, l'auteur évoque une gestion sans coût des exceptions mais ne semble pas la décrire. De quoi s'agit-il?
    En fait, c'est la méthode qu'utilisent les compilateurs. La méthode zero-cost et la méthode setjmp/longjmp sont juste deux façons pour le compilateur de compiler les exceptions, en fait.

    Citation Envoyé par Aleph69 Voir le message
    @Joel F : je n'ai pas compris pourquoi une assertion devrait être levée dans le cas particulier du divide. Cela ne va-t-il pas interrompre prématurément le déroulement du programme si on fait cela?
    En fait, Joel expliquait que si on appelle divide avec un dénominateur de zéro, alors l'erreur vient du dév', et donc qu'il n'est pas gênant de tout faire crasher.
    Pour lui faire plaisir, on pourrait appeler la fonction divide_protected ; ou quelque chose du genre.

  6. #6
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Citation Envoyé par Ekleog Voir le message
    En fait, c'est la méthode qu'utilisent les compilateurs. La méthode zero-cost et la méthode setjmp/longjmp sont juste deux façons pour le compilateur de compiler les exceptions, en fait.
    Ah oui, effectivement c'est précisé dans le billet.
    Y a-t-il un moyen de tester le choix fait par le compilateur?
    Que font gcc et le compilateur de visual?

    Citation Envoyé par Ekleog Voir le message
    En fait, Joel expliquait que si on appelle divide avec un dénominateur de zéro, alors l'erreur vient du dév', et donc qu'il n'est pas gênant de tout faire crasher.
    Pour lui faire plaisir, on pourrait appeler la fonction divide_protected ; ou quelque chose du genre.
    Je ne suis pas tout à fait sûr de cela. La division par zéro pour des flottants n'est pas forcément une erreur de développeur, elle apparaît dans plein de méthodes numériques, et je ne pourrais pas prétendre que ce n'est pas le cas non plus pour des entiers. Qu'est-ce qui t'amène à penser cela?

  7. #7
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    On parle de divisione entière par 0 qui sur les proc bien elevée est un TRAP.
    0.f/0.F ca vaut Nan.f on est bien d'accord.

    BIen utiliser les exceptions ca va avec les utiliser la ou elles ont du sens; aka les postconditions. Les preconditions ca assert.

  8. #8
    Membre expérimenté
    Homme Profil pro
    Chercheur
    Inscrit en
    Mars 2010
    Messages
    1 218
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Chercheur

    Informations forums :
    Inscription : Mars 2010
    Messages : 1 218
    Points : 1 685
    Points
    1 685
    Par défaut
    Oui, oui bien bien sûr. Je sais qu'en général les compilateurs lèvent une assertion pour les entiers et par pour les flottants mais je ne vois pas ce qui justifie a priori cette distinction sachant que la norme indique bien qu'il s'agit d'un comportement indéterminé. Je n'ai pas non plus d'argument en faveur du fait de lever une exception. C'est juste que vu ta remarque et le fait que tu ais développé une bibliothèque d'algèbre linéaire, je croyais que tu avais une bonne raison de lever une assertion pour les entiers et pas pour les flottants et je voulais juste en savoir plus! Tout cela est un peu hors sujet donc j'arrête là!

  9. #9
    Membre chevronné
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Points : 1 921
    Points
    1 921
    Par défaut
    0/0 c'est pas le compilo qui couine, mais le proc qui va TRAPPER.
    0/0 c'ets indefini donc tu ne veux pas que ca se fasse du tout. La ou en reel, 0/0 est NaN et est bien défini.

  10. #10
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Citation Envoyé par Joel F Voir le message
    BIen utiliser les exceptions ca va avec les utiliser la ou elles ont du sens; aka les postconditions. Les preconditions ca assert.
    100% d'accord avec ça.
    Find me on github

  11. #11
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Décembre 2010
    Messages
    734
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Décembre 2010
    Messages : 734
    Points : 1 475
    Points
    1 475
    Par défaut
    Placez-vous les règles de gestion et autres contraintes de cohérence de base de données dans les postconditions (vraie question inside)?

  12. #12
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    De base de donnée? Personellement pas spécifiquement, mais les contraintes du model, quel que soit l'implémentation, oui.

    La différence c'est que selon le type de vérification et le type d'implémentation, le model peut ou pas être vérifié en amont de la base de donnée.


    Je sais pas si ça réponds à ta question.

  13. #13
    screetch
    Invité(e)
    Par défaut
    l'exemple utilise est tres bancal, il n'y a pas un destructeur a appeler et le code a executer est minimal.

  14. #14
    Membre éclairé
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Points : 879
    Points
    879
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <iostream>
    struct S {
       ~S();
    };
    S::~S() {
       std::cout << "~S" << std::endl;
    }
    void foo(int i) {
       S s;
       if (i <= 0) throw "fail"; // Oui, c'est debile.
       foo(i - 1);
    }
    int main() {
       foo(5);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    (gdb) disas main
    Dump of assembler code for function main():
       0x0000000000400a00 <+0>:     sub    rsp,0x8
       0x0000000000400a04 <+4>:     mov    edi,0x5
       0x0000000000400a09 <+9>:     call   0x400bb0 <foo(int)>
       0x0000000000400a0e <+14>:    xor    eax,eax
       0x0000000000400a10 <+16>:    add    rsp,0x8
       0x0000000000400a14 <+20>:    ret
    End of assembler dump.
    => Rien de particulier. Pas de try, normal. On pourrait mettre un try pour voir ce que ça donne, aussi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    int main() {
       try {
          foo(5);
       } catch(...) {
          std::cout << "(!!)" << std::endl;
       }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    (gdb) disas main
    Dump of assembler code for function main():
       0x0000000000400ba0 <+0>:     push   rbx
       0x0000000000400ba1 <+1>:     mov    edi,0x5
       0x0000000000400ba6 <+6>:     call   0x400d80 <foo(int)>
       0x0000000000400bab <+11>:    xor    eax,eax
       0x0000000000400bad <+13>:    pop    rbx
       0x0000000000400bae <+14>:    ret    
       0x0000000000400baf <+15>:    mov    rdi,rax
       0x0000000000400bb2 <+18>:    call   0x400b60 <__cxa_begin_catch@plt>
       0x0000000000400bb7 <+23>:    mov    esi,0x400e8c
       0x0000000000400bbc <+28>:    mov    edi,0x601320
       0x0000000000400bc1 <+33>:    call   0x400af0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
       0x0000000000400bc6 <+38>:    mov    rdi,rax
       0x0000000000400bc9 <+41>:    call   0x400b50 <_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@plt>
       0x0000000000400bce <+46>:    call   0x400b40 <__cxa_end_catch@plt>
       0x0000000000400bd3 <+51>:    jmp    0x400bab <main()+11>
       0x0000000000400bd5 <+53>:    mov    rbx,rax
       0x0000000000400bd8 <+56>:    call   0x400b40 <__cxa_end_catch@plt>
       0x0000000000400bdd <+61>:    mov    rdi,rbx
       0x0000000000400be0 <+64>:    call   0x400b80 <_Unwind_Resume@plt>
    End of assembler dump.
    A l'exception des sub/add rsp remplacés par des push/pop ; le chemin d'exécution normal est le même.
    Par contre, comme attendu, le chemin d'exécution exceptionnel est plus complexe.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    (gdb) disas foo
    Dump of assembler code for function foo(int):
       0x0000000000400bb0 <+0>:     push   rbx
       0x0000000000400bb1 <+1>:     sub    rsp,0x10
       0x0000000000400bb5 <+5>:     test   edi,edi
       0x0000000000400bb7 <+7>:     jle    0x400bd1 <foo(int)+33>
       0x0000000000400bb9 <+9>:     sub    edi,0x1
       0x0000000000400bbc <+12>:    call   0x400bb0 <foo(int)>
       0x0000000000400bc1 <+17>:    lea    rdi,[rsp+0xf]
       0x0000000000400bc6 <+22>:    call   0x400b40 <S::~S()>
       0x0000000000400bcb <+27>:    add    rsp,0x10
       0x0000000000400bcf <+31>:    pop    rbx
       0x0000000000400bd0 <+32>:    ret    
       0x0000000000400bd1 <+33>:    mov    edi,0x8
       0x0000000000400bd6 <+38>:    call   0x4009b0 <__cxa_allocate_exception@plt>
       0x0000000000400bdb <+43>:    xor    edx,edx
       0x0000000000400bdd <+45>:    mov    QWORD PTR [rax],0x400cb7
       0x0000000000400be4 <+52>:    mov    esi,0x6013e0
       0x0000000000400be9 <+57>:    mov    rdi,rax
       0x0000000000400bec <+60>:    call   0x4009c0 <__cxa_throw@plt>
       0x0000000000400bf1 <+65>:    lea    rdi,[rsp+0xf]
       0x0000000000400bf6 <+70>:    mov    rbx,rax
       0x0000000000400bf9 <+73>:    call   0x400b40 <S::~S()>
       0x0000000000400bfe <+78>:    mov    rdi,rbx
       0x0000000000400c01 <+81>:    call   0x4009e0 <_Unwind_Resume@plt>
    End of assembler dump.
    Le chemin du throw est assez lourd, comme on peut s'y attendre.
    Il alloue, puis "throw", puis appelle le destructeur, et enfin appelle _Unwind_Resume@plt qui, je suppose, remonte la pile d'appel.

    Si je comprends bien, voici ce qui se passe (g++) :
    • __cxa_allocate_exception@plt alloue de l'espace pour une exception ;
    • __cxa_throw@plt emploie cet espace pour y stocker toutes les données de l'exception (type, contenu...) ;
    • On appelle les destructeurs de tous les objets ;
    • _Unwind_Result@plt remonte la pile à la recherche d'une fonction ayant un code de gestion des exceptions, c'est-à-dire d'un code situé juste après le ret ; puis il jmp dans ce code (il doit à avoir un pointeur vers le code dans une globale constante quelque part) ;
    • Ce code commence par tester le type de l'exception ramassée (testé avec std::string const & à la place de ...), soit il appelle _Unwind_Resume (échec du match) ; soit il appelle __cxa_begin_catch ;
    • __cxa_begin_catch... enregistre qu'on est entré dans un catch ? __cxa_end_catch correspond... je ne connais pas leur utilité... Peut-être __cxa_begin_catch dispatche-t-il vers le bon code de gestion d'erreurs ?
    • Le code de gestion d'erreurs est appellé ;
    • Si il y a un rethrow, les destructeurs sont appellés et _Unwind_Resume@plt est réappellé ;
    • Sinon, le code retourne à son exécution normale ;
    • Enfin, si aucun handler ne convient, _Unwind_Resume@plt est appellé, et on recommence un tour avec une fonction de moins sur la stack.


    Est-ce un exemple suffisamment complexe ?

  15. #15
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Non, dans ton premier exemple, ta fonction est appelée avec une constante numérique intégrale, le test est très simple et est donc résolu à la compilation (a tous les coups). Le compilo a du éliminer la branche qui fait le throw.

    Si tu veux un exemple qui est plus probable:

    1. fais en sorte que les données à traiter viennent de l'extérieur, pour empecher le compilo de faire du traitement à la compilation
    2. fais en sorte qu'il y ait plusieurs unités de compilations (.cpp)

    L'idéal serait de faire un petit jeu, mais même comme ça, selon le type de performance nécessaire, ça risque d'être difficile à mesurer.

  16. #16
    Membre éclairé
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Points : 879
    Points
    879
    Par défaut
    Citation Envoyé par Klaim Voir le message
    Non, dans ton premier exemple, ta fonction est appelée avec une constante numérique intégrale, le test est très simple et est donc résolu à la compilation (a tous les coups). Le compilo a du éliminer la branche qui fait le throw.
    Le throw étant dans foo et non dans main, il est normal que dans le premier disas il n'y ait pas de branche, hein. Ou bien je comprends mal ?

    Si tu veux un exemple qui est plus probable:

    1. fais en sorte que les données à traiter viennent de l'extérieur, pour empecher le compilo de faire du traitement à la compilation
    2. fais en sorte qu'il y ait plusieurs unités de compilations (.cpp)

    L'idéal serait de faire un petit jeu, mais même comme ça, selon le type de performance nécessaire, ça risque d'être difficile à mesurer.
    Sauf qu'à en voir l'asm, il n'y a pas tellement d'optimisations faites.
    Dans chaque fonction, on voit bien chaque branche... on reconnaît même presque le code C++ directement dans l'asm (bon, d'accord, il faut regarder de loin, mais si on e fait attention qu'aux call on s'y retrouve pas mal -- à part les flux qui font un bazar assez effrayant).
    Du coup, je ne suis pas sûr qu'on gagnerait beaucoup en réalisme en complexifiant l'affaire. (loi des 80/20)

  17. #17
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Citation Envoyé par Ekleog Voir le message
    Sauf qu'à en voir l'asm, il n'y a pas tellement d'optimisations faites.)
    Les as-tu bien toutes demandées au moment de la compil ?
    Find me on github

  18. #18
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Citation Envoyé par Ekleog Voir le message
    Le throw étant dans foo et non dans main, il est normal que dans le premier disas il n'y ait pas de branche, hein. Ou bien je comprends mal ?
    Ca n'a rien a voir. Ta fonction est définie dans la meme unitée de compilation, le compilo peut inliner, et même si il n'inline pas, il peut immédiatement deviner que la valeur passée est constante et donc qu'il n'y a qu'un seul chemin possible et donc qu'il peut éliminer le reste, d'ou le code que tu obtiens.


    Sauf qu'à en voir l'asm, il n'y a pas tellement d'optimisations faites.
    Dans chaque fonction, on voit bien chaque branche... on reconnaît même presque le code C++ directement dans l'asm (bon, d'accord, il faut regarder de loin, mais si on e fait attention qu'aux call on s'y retrouve pas mal -- à part les flux qui font un bazar assez effrayant).
    Du coup, je ne suis pas sûr qu'on gagnerait beaucoup en réalisme en complexifiant l'affaire. (loi des 80/20)
    Si, parcequ'il est question de comparaison. Le souci c'est que dans ton premier exemple, il est tellement simple (le throw est viré) qu'il n'est pas un bon point de comparaison.
    Et aussi parceque l'impact de la présence de throw (sans try catch) n'est réllement visible que dans des applications réélles, a cause de leur complexité qui n'est jamais aussi simple que ton exemple. Notemment parcequ'il ya, par nature, énormément plus de sauts, de branches et de découpages en petites fonctions, de manipulations (visible ou pas) de pointeurs, etc.

    Selon le type d'implication, le cout peut être tout a fait différent.
    Ah oui aussi: il faut comparer avec une application qui ne fait pas de throw explicite (mais qui utilise la STL). Tu vas voir ça a un autre gout.

  19. #19
    Membre éclairé
    Avatar de Ekleog
    Homme Profil pro
    Étudiant
    Inscrit en
    Janvier 2012
    Messages
    448
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2012
    Messages : 448
    Points : 879
    Points
    879
    Par défaut
    Citation Envoyé par jblecanard Voir le message
    Les as-tu bien toutes demandées au moment de la compil ?
    De mémoire, la commandé était g++ -g a.cpp avec alias g++='LANG="C" g++ -std=c++11 -O2 -Wall -Wextra -pedantic' (oui, j'ai ramassé un warning unused s)

    Citation Envoyé par Klaim Voir le message
    Ca n'a rien a voir. Ta fonction est définie dans la meme unitée de compilation, le compilo peut inliner, et même si il n'inline pas, il peut immédiatement deviner que la valeur passée est constante et donc qu'il n'y a qu'un seul chemin possible et donc qu'il peut éliminer le reste, d'ou le code que tu obtiens.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // a.cpp
    #include <iostream>
    void bar(int i);
    int main() {
       try {
          bar(5);
       } catch(std::string const &) {
          std::cout << "(!!)" << std::endl;
       }
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    // b.cpp
    void foo(int i);
    void bar(int i) {
       foo(i);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // c.cpp
    struct S {
       ~S();
    };
    void foo(int i) {
       S s;
       if (i <= 0) throw "fail"; // Oui, c'est debile.
       foo(i - 1);
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    // d.cpp
    #include <iostream>
    struct S {
       ~S();
    };
    S::~S() {
       std::cout << "~S" << std::endl;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    tmp$ # compilation
    tmp$ alias g++
    alias g++='LANG="C" g++ -std=c++11 -O2 -Wall -Wextra -pedantic'
    tmp$ g++ -c a.cpp -o a.o
    tmp$ g++ -c b.cpp -o b.o
    tmp$ g++ -c c.cpp -o c.o
    c.cpp: In function 'void foo(int)':
    c.cpp:5:6: warning: unused variable 's' [-Wunused-variable]
    tmp$ g++ -c d.cpp -o d.o
    tmp$ g++ a.o b.o c.o d.o -o exec
    tmp$ gdb exec
    => (Même pas -flto ni -O3, tu noteras.)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    (gdb) disas main
    Dump of assembler code for function main:
       0x0000000000400bf0 <+0>:     push   rbx
       0x0000000000400bf1 <+1>:     mov    edi,0x5
       0x0000000000400bf6 <+6>:     call   0x400da0 <_Z3bari>
       0x0000000000400bfb <+11>:    xor    eax,eax
       0x0000000000400bfd <+13>:    pop    rbx
       0x0000000000400bfe <+14>:    ret
       0x0000000000400bff <+15>:    sub    rdx,0x1
       0x0000000000400c03 <+19>:    mov    rdi,rax
       0x0000000000400c06 <+22>:    je     0x400c0d <main+29>
       0x0000000000400c08 <+24>:    call   0x400bd0 <_Unwind_Resume@plt>
       0x0000000000400c0d <+29>:    call   0x400bb0 <__cxa_begin_catch@plt>
       0x0000000000400c12 <+34>:    mov    esi,0x400f24
       0x0000000000400c17 <+39>:    mov    edi,0x601480
       0x0000000000400c1c <+44>:    call   0x400b40 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
       0x0000000000400c21 <+49>:    mov    rdi,rax
       0x0000000000400c24 <+52>:    call   0x400ba0 <_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@plt>
       0x0000000000400c29 <+57>:    call   0x400b90 <__cxa_end_catch@plt>
       0x0000000000400c2e <+62>:    xchg   ax,ax
       0x0000000000400c30 <+64>:    jmp    0x400bfb <main+11>
       0x0000000000400c32 <+66>:    mov    rbx,rax
       0x0000000000400c35 <+69>:    call   0x400b90 <__cxa_end_catch@plt>
       0x0000000000400c3a <+74>:    mov    rdi,rbx
       0x0000000000400c3d <+77>:    call   0x400bd0 <_Unwind_Resume@plt>
    End of assembler dump.
    => Me semble bien proche de l'exemple précédent
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    (gdb) disas bar
    Dump of assembler code for function _Z3bari:
       0x0000000000400da0 <+0>:     jmp    0x400db0 <_Z3fooi>
    End of assembler dump.
    => J'ai rajouté la fonction ; elle n'a rien à voir avec les erreurs et n'a pas à y gérer quoi que ce soit.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    (gdb) disas foo
    Dump of assembler code for function _Z3fooi:
       0x0000000000400db0 <+0>:     push   rbx
       0x0000000000400db1 <+1>:     sub    rsp,0x10
       0x0000000000400db5 <+5>:     test   edi,edi
       0x0000000000400db7 <+7>:     jle    0x400dd1 <_Z3fooi+33>
       0x0000000000400db9 <+9>:     sub    edi,0x1
       0x0000000000400dbc <+12>:    call   0x400db0 <_Z3fooi>
       0x0000000000400dc1 <+17>:    lea    rdi,[rsp+0xf]
       0x0000000000400dc6 <+22>:    call   0x400e10 <_ZN1SD2Ev>
       0x0000000000400dcb <+27>:    add    rsp,0x10
       0x0000000000400dcf <+31>:    pop    rbx
       0x0000000000400dd0 <+32>:    ret
       0x0000000000400dd1 <+33>:    mov    edi,0x8
       0x0000000000400dd6 <+38>:    call   0x400b70 <__cxa_allocate_exception@plt>
       0x0000000000400ddb <+43>:    xor    edx,edx
       0x0000000000400ddd <+45>:    mov    QWORD PTR [rax],0x400f43
       0x0000000000400de4 <+52>:    mov    esi,0x6015a0
       0x0000000000400de9 <+57>:    mov    rdi,rax
       0x0000000000400dec <+60>:    call   0x400b80 <__cxa_throw@plt>
       0x0000000000400df1 <+65>:    lea    rdi,[rsp+0xf]
       0x0000000000400df6 <+70>:    mov    rbx,rax
       0x0000000000400df9 <+73>:    call   0x400e10 <_ZN1SD2Ev>
       0x0000000000400dfe <+78>:    mov    rdi,rbx
       0x0000000000400e01 <+81>:    call   0x400bd0 <_Unwind_Resume@plt>
    End of assembler dump.
    => Me semble identique à la version précédente.

    => A part que cette fois il ne me demangle plus les fonctions (je me demande bien pourquoi, d'ailleurs, vu que je n'ai rien modifié), aucune différence.

    Si, parcequ'il est question de comparaison. Le souci c'est que dans ton premier exemple, il est tellement simple (le throw est viré) qu'il n'est pas un bon point de comparaison.
    C'est d'ailleurs pour ça que j'ai fait un deuxième exemple, hein. :°

    Et aussi parceque l'impact de la présence de throw (sans try catch) n'est réllement visible que dans des applications réélles, a cause de leur complexité qui n'est jamais aussi simple que ton exemple. Notemment parcequ'il ya, par nature, énormément plus de sauts, de branches et de découpages en petites fonctions, de manipulations (visible ou pas) de pointeurs, etc.
    Mes fonctions ne sont pas assez petites ?

    Selon le type d'implication, le cout peut être tout a fait différent.
    implication ?

    Ah oui aussi: il faut comparer avec une application qui ne fait pas de throw explicite (mais qui utilise la STL). Tu vas voir ça a un autre gout.
    Pourquoi la SL aurait-elle un résultat différent ? A part l'inline qui rend l'assembleur illisible, je ne vois pas ce qui devrait faire que ça change. Surtout vu que la SL est implémentée surtout en headers, et que dans ces headers on voit des throw. Bon, ils sont sous la forme de __throw_out_of_range ; mais je suppose que l'implémentation est très similaire.

    Regardons :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    (gdb) disas _ZSt16__throw_bad_castv
    Dump of assembler code for function _ZSt16__throw_bad_castv@plt:
       0x0000000000400ad0 <+0>:     jmp    QWORD PTR [rip+0x2008a2]        # 0x601378 <_ZSt16__throw_bad_castv@got.plt>
       0x0000000000400ad6 <+6>:     push   0x0
       0x0000000000400adb <+11>:    jmp    0x400ac0
    End of assembler dump.
    (gdb) x/a 0x601378
    0x601378 <_ZSt16__throw_bad_castv@got.plt>:     0x400ad6 <_ZSt16__throw_bad_castv@plt+6>
    Bon, c'est bizarre cette façon de jmp à l'instruction suivante, mais bon...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    (gdb) x/2i 0x400ac0
       0x400ac0:    push   QWORD PTR [rip+0x2008a2]        # 0x601368 <_GLOBAL_OFFSET_TABLE_+8>
       0x400ac6:    jmp    QWORD PTR [rip+0x2008a4]        # 0x601370 <_GLOBAL_OFFSET_TABLE_+16>
    (gdb) x/a 0x601370
    0x601370 <_GLOBAL_OFFSET_TABLE_+16>:    0x0
    Et là... je ne comprends pas. Pourquoi un jmp 0x0 ?
    Je suppose que la GOT doit être manipulée d'une façon ou d'une autre par les fonctions _start ou ld-linux.so ou autre... même si je croyais me souvenir qu'elle était statique.

  20. #20
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Ce que je veux dire c'est qu'il faut comparer ce qui est comparable. Une trop petite application ne reflette pas les différences de performance trouvés sur les applications en cours de dev ou finies.

    Ces exemples sont trop simples parcequ'ils ne sont pas aussi complexe qu'un bete shoot them up par exemple.

    Donc, en gros, il faut vraiment tester sur une application complete.

    Mais selon le type d'applications, tu vas avoir des goulots d'etranglement différents, et les variations de perfs potentielles liées aux exceptions vont aussi varier.

    Donc en gros, pour avoir un test vraiment probant il faut soit:

    1. une appli déjà complete qui a des macro pour pouvoir comparer les différents cas
    2. ecrire cette appli si elle n'existe pas
    3. avoir des différentes versions de l'appli ou differentes applis qui soient construitent avec une architecture totalement différente.

    ET les comparer.

    C'est très chiant a faire, c'est pour ça que personne le fais. Mais quand t'as, par exemple, un jeu complet, et que tu tests la différence entre avec et sans exceptions , sans avoir écris aucun throw ou try/catch, la dernière fois que j'ai essayé j'ai gagné en perf en enlevant la gestion des exception (qui n'était pas utilisé, sauf dans la stl).

    Donc faudrait pouvoir refaire le même genre de test avec les compilos récents.

Discussions similaires

  1. Gestion des exception (EOleException)
    Par shurized dans le forum Bases de données
    Réponses: 5
    Dernier message: 30/06/2004, 18h25
  2. Performance des vertex array
    Par Mathieu.J dans le forum OpenGL
    Réponses: 13
    Dernier message: 25/06/2004, 11h47
  3. [XMLRAD] gestion des exceptions
    Par pram dans le forum XMLRAD
    Réponses: 2
    Dernier message: 28/01/2003, 18h48
  4. c: gestion des exceptions
    Par vince_lille dans le forum C
    Réponses: 7
    Dernier message: 05/06/2002, 15h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo