Est finalement cher

14

En cas de code où vous devez faire un nettoyage de ressource avant de quitter une fonction, y a-t-il une différence de performance majeure entre ces 2 façons de le faire.

  1. Nettoyage de la ressource avant chaque déclaration de retour

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Nettoyage de la ressource dans un bloc enfin

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

J'ai fait quelques tests de base dans des exemples de programmes et il ne semble pas y avoir beaucoup de différence. Je préfère tellement la finallyfaçon de procéder - mais je me demandais si cela entraînerait un impact sur les performances d'un grand projet.

user93353
la source
3
Cela ne vaut pas vraiment la peine d'être publié comme réponse, mais non. Si vous utilisez Java, qui s'attend à ce que les exceptions soient la stratégie de gestion des erreurs par défaut, vous devez utiliser try / catch / enfin - le langage est optimisé pour le modèle que vous utilisez.
Jonathan Rich
1
@JonathanRich: comment ça?
haylem
2
Écrivez le code proprement de telle manière qu'il exprime ce que vous essayez d'accomplir. Optimisez plus tard lorsque vous savez que vous avez un problème - n'évitez pas les fonctionnalités de langue car vous pensez que vous pourriez réduire de quelques millisecondes quelque chose qui n'est pas lent en premier lieu.
Sean McSomething
@mikeTheLiar - Si vous voulez dire que je devrais écrire if(!cond), alors c'est Java qui m'a fait faire ça. En C ++, c'est ainsi que j'écris du code à la fois des booléens et aussi pour d'autres types - ie int x; if(!x). Étant donné que java ne me permet de l'utiliser que pour booleans, j'ai complètement cessé d'utiliser le if(cond)& if(!cond)in java.
user93353
1
@ user93353 qui me rend triste. Je préférerais de beaucoup voir plutôt (someIntValue != 0)que comparer plutôt qu'évaluer les booléens. Cela me sent, et je le refaçonne immédiatement quand je le vois à l'état sauvage.
MikeTheLiar

Réponses:

23

Comme indiqué dans Quelle est la lenteur des exceptions Java? on peut voir que la lenteur de se try {} catch {}situe dans l'instauration de l'exception elle-même.

La création d'une exception récupère la pile d'appels entière depuis le runtime et c'est là que se situe la dépense. Si vous ne créez pas d'exception, il ne s'agit que très légèrement d'une augmentation de temps.

Dans l'exemple donné dans cette question, il n'y a pas d'exceptions, on ne s'attendrait pas à un ralentissement de leur création - ils ne sont pas créés. Au lieu de cela, il s'agit ici try {} finally {}de gérer la désallocation des ressources dans le bloc finally.

Donc, pour répondre à la question, non, il n'y a pas de réel coût d'exécution dans une try {} finally {}structure qui n'utilise pas d'exceptions (ce n'est pas inconnu, comme on le voit). Ce qui est peut - être coûteux, c'est le temps de maintenance lorsque l'on lit le code et voit ce style de code non typique et que le codeur doit se rappeler que quelque chose d'autre se produit dans cette méthode après le returnavant de revenir à l'appel précédent.


Comme cela a été mentionné, la maintenance est un argument pour les deux façons de procéder. Pour mémoire, après examen, ma préférence serait l'approche finale.

Considérez le temps nécessaire pour enseigner à quelqu'un une nouvelle structure linguistique. Voir try {} finally {}n'est pas quelque chose que l'on voit souvent dans le code Java et peut donc être source de confusion pour les gens. Il y a un certain temps de maintenance pour apprendre des structures un peu plus avancées en Java que ce que les gens connaissent.

Le finally {}bloc s'exécute toujours . Et c'est pourquoi vous devriez l'utiliser. Considérez également le temps de maintenance du débogage de l'approche non-finale lorsque quelqu'un oublie d'inclure une déconnexion au bon moment, ou l'appelle au mauvais moment, ou oublie de retourner / quitter après l'avoir appelée afin qu'elle soit appelée deux fois. Il y a tellement de bugs possibles avec cela que l'utilisation de try {} finally {}rend impossible.

Lorsque l'on pèse ces deux coûts, il est coûteux en temps de maintenance de ne pas utiliser l' try {} finally {}approche. Alors que les gens peuvent hésiter sur le nombre de millisecondes fractionnaires ou d'instructions jvm supplémentaires que le try {} finally {}bloc est comparé à l'autre version, il faut également considérer les heures passées à déboguer le moyen moins qu'idéal de traiter la désallocation des ressources.

Écrivez d'abord le code maintenable, et de préférence de manière à empêcher l'écriture ultérieure des bogues.

Communauté
la source
1
Une modification non suggérée a dit ainsi: "essayez / enfin offre des garanties que vous n'auriez pas autrement concernant les exceptions d'exécution. Que cela vous plaise ou non, n'importe quelle ligne est pratiquement capable de lever une exception" - Moi: ce qui n'est pas exactement vrai . La structure donnée dans la question n'a pas d' exceptions supplémentaires qui pourraient ralentir les performances du code. Il n'y a aucun impact supplémentaire sur les performances en enroulant un bloc try / finally autour du bloc par rapport au bloc non-try / finally.
2
Ce qui pourrait être encore plus coûteux en maintenance que de comprendre cette utilisation de try-finally, est de devoir entretenir plusieurs blocs de nettoyage. Maintenant, au moins, vous savez que si un nettoyage supplémentaire est nécessaire, il n'y a qu'un seul endroit pour le mettre.
Bart van Ingen Schenau
@BartvanIngenSchenau En effet. Un essai final est probablement le meilleur moyen de le gérer, ce n'est tout simplement pas si commun d'une structure que j'ai vue dans le code - les gens ont assez de mal à essayer de comprendre essayer finalement et la gestion des exceptions en général .. mais essayez -finalement sans prise? Quelle? Les gens ne le comprennent vraiment pas . Comme vous l'avez suggéré, il vaut mieux comprendre la structure du langage que d'essayer de l'éviter et de mal faire les choses.
Je voulais surtout faire savoir que l'alternative à essayer enfin n'est pas gratuite non plus. J'aimerais pouvoir donner un autre vote positif pour votre ajout sur les coûts.
Bart van Ingen Schenau
5

/programming/299068/how-slow-are-java-exceptions

La réponse acceptée à cette question montre que l'encapsulation d'un appel de fonction dans un bloc try catch coûte moins de 5% par rapport à un appel de fonction nu. En réalité, le fait de lancer et de rattraper l'exception a provoqué une exécution de la montgolfière à plus de 66 fois l'appel de fonction nue. Donc, si vous vous attendez à ce que la conception des exceptions se produise régulièrement, j'essayerais de l'éviter (dans un code critique de performances correctement profilé), mais si la situation exceptionnelle est rare, ce n'est pas un gros problème.

stonemetal
la source
3
"si la situation exceptionnelle est rare" - et bien il y a une tautologie juste là. Exceptionnel signifie rare et c'est précisément la façon dont les exceptions doivent être utilisées. Ne doit jamais faire partie du flux de conception ou de contrôle.
Jesse C. Slicer
1
Les exceptions ne sont pas nécessairement rares dans la pratique, c'est juste un effet secondaire accidentel. Par exemple, si vous avez une mauvaise interface utilisateur, les personnes peuvent provoquer le flux de cas d'utilisation d'exception plus souvent que le flux normal
Esailija
2
Aucune exception n'est levée dans le code de la question.
2
@ JesseC.Slicer Il y a des langues où il n'est pas rare de les voir utilisées comme contrôle de flux. Par exemple, lors de l'itération de quelque chose en python, l'itérateur lève une exception de stopitération lorsqu'il est terminé. De plus, dans OCaml, leur bibliothèque standard semble être très "heureuse", elle lance dans un grand nombre de situations qui ne sont pas rares (si vous avez une carte et essayez de rechercher quelque chose qui n'existe pas dans la carte qu'elle lance) ).
stonemetal
@stonemetal Comme le fait un dict Python, avec un KeyError
Izkata
4

Au lieu d'optimiser - pensez à ce que fait votre code.

L'ajout du bloc finally est une implémentation différente - cela signifie que vous vous déconnecterez de chaque exception.

Plus précisément - si la connexion lève une exception, le comportement de votre deuxième fonction sera différent de votre premier.

Si la connexion et la déconnexion ne font pas réellement partie du comportement de la fonction, je suggère que la méthode fasse bien une chose et une chose:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

et devrait être dans une fonction qui est déjà encapsulée dans un contexte qui gère la connexion / déconnexion, car ceux-ci semblent fondamentalement différents de ce que fait votre code principal.

Votre message est étiqueté comme Java que je ne connais pas très bien mais en .net, j'utiliserais un bloc à l'aide de ceci:

c'est à dire:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

Et le contexte de connexion a une méthode Dispose qui déconnectera et nettoiera les ressources.

Edit: je n'ai pas vraiment répondu à la question!

Je n'ai aucune preuve mesurable, mais je ne m'attendrais pas à ce que cela soit plus cher ou certainement pas suffisamment cher pour qu'il soit digne d'une optimisation prématurée.

Je vais laisser le reste de ma réponse car bien que je ne réponde pas à la question, je pense que c'est utile pour le PO.

Michael
la source
1
Pour compléter votre réponse, Java 7 a introduit une instruction try-with-resources qui s'apparenterait à votre usingconstruction .net , en supposant que la ressource en question implémente l' AutoCloseableinterface.
haylem
Je supprimerais même cette retvariable, qui vient d'être affectée deux fois pour être vérifiée une ligne plus tard. Faites-le if (dosomething() == false) return;.
ott--
D'accord, mais je voulais garder le code OP tel qu'il était fourni.
Michael
@ ott-- Je suppose que le code de l'OP est une simplification de leur cas d'utilisation - par exemple, ils pourraient avoir quelque chose de plus similaire ret2 = doSomethingElse(ret1), mais ce n'est pas pertinent pour la question, il a donc été supprimé.
Izkata
if (dosomething() == false) ...est un mauvais code. Utilisez le plus intuitifif (!dosomething()) ...
Steffen Heil
1

Hypothèse: vous développez en code C #.

La réponse rapide est qu'il n'y a aucun impact significatif sur les performances de l'utilisation des blocs try / finally. Il y a un impact sur les performances lorsque vous lancez et interceptez des exceptions.

La réponse la plus longue est de voir par vous-même. Si vous regardez le code CIL sous-jacent généré ( par exemple, le réflecteur à grille rouge, vous pouvez voir les instructions CIL sous-jacentes générées et voir l'impact de cette façon.

Michael Shaw
la source
11
La question est taguée java .
Joachim Sauer
Bien repéré! ;)
Michael Shaw
3
De plus, les méthodes ne sont pas capitalisées, bien que cela puisse être de bon goût.
Erik Reppen
toujours une bonne réponse si la question était c # :)
PhillyNJ
-2

Comme d'autres l'ont déjà noté, le code Ttese aura des résultats différents, si l'un dosomethingelseou l' autre lèvent dootherstuffune exception.

Et oui, tout appel de fonction peut lever une exception, au moins un StackOVerflowError! Bien qu'il soit rarement possible de les gérer correctement (car toute fonction de nettoyage appelée aura probablement le même problème, dans certains cas (comme l'implémentation de verrous par exemple), il est crucial de gérer cela correctement ...

Steffen Heil
la source
2
cela ne semble pas offrir quoi que ce soit de substantiel par rapport aux 6 réponses précédentes
moucher