216 votes

Est-il coûteux d'utiliser des blocs try-catch même si une exception n'est jamais levée ?

Nous savons qu'il est coûteux d'attraper les exceptions. Mais est-il également coûteux d'utiliser un bloc try-catch en Java, même si une exception n'est jamais levée ?

J'ai trouvé la question/réponse de Stack Overflow Pourquoi les blocs d'essai sont-ils chers ? mais c'est pour .NET .

0 votes

Pour autant que je sache, un bloc try est très bon marché en Java.

32 votes

Cette question n'a vraiment aucun sens. Essayez catch a un but très précis. Si vous en avez besoin, vous en avez besoin. De toute façon, à quoi sert un try sans un catch ?

1 votes

Est-il possible d'avoir un try sans catch à moins d'utiliser un finally dans le bloc ?

231voto

Patashu Points 14053

try n'a presque aucune dépense. Au lieu de faire le travail de mise en place de la try au moment de l'exécution, les métadonnées du code sont structurées au moment de la compilation de telle sorte que, lorsqu'une exception est levée, le code effectue une opération relativement coûteuse consistant à remonter la pile et à vérifier si une exception a été levée. try Il n'existe pas de blocs qui permettraient d'attraper cette exception. Du point de vue d'un profane, try peut aussi bien être libre. C'est en fait le lancement de l'exception qui vous coûte - mais à moins que vous ne lanciez des centaines ou des milliers d'exceptions, vous ne remarquerez pas le coût.


try a quelques coûts mineurs qui lui sont associés. Java ne peut pas effectuer certaines optimisations sur le code d'un fichier try qu'il le ferait autrement. Par exemple, Java réorganisera souvent les instructions d'une méthode pour la rendre plus rapide, mais Java doit également garantir que si une exception est levée, l'exécution de la méthode est observée comme si ses instructions, telles qu'elles sont écrites dans le code source, s'exécutaient dans l'ordre jusqu'à une certaine ligne.

Parce que dans un try une exception peut être levée (à n'importe quelle ligne du bloc d'essai ! Certaines exceptions sont levées de manière asynchrone, par exemple en appelant stop sur un Thread (qui est déprécié), et même en dehors de cela OutOfMemoryError peut se produire presque n'importe où) et pourtant il peut être attrapé et le code continuer à s'exécuter ensuite dans la même méthode, il est plus difficile de raisonner sur les optimisations qui peuvent être faites, de sorte qu'ils sont moins susceptibles de se produire. (Quelqu'un devrait programmer le compilateur pour qu'il les fasse, raisonner et garantir la correction, etc. Ce serait un gros problème pour quelque chose qui est censé être "exceptionnel".) Mais encore une fois, en pratique, vous ne remarquerez pas ce genre de choses.

2 votes

Certaines exceptions sont lancées de manière asynchrone ils ne sont pas asynchrones mais lancés dans des points sûrs. et cette partie essayer a quelques coûts mineurs qui lui sont associés. Java ne peut pas effectuer certaines optimisations sur le code dans un bloc try qu'il aurait pu faire autrement. a besoin d'une référence sérieuse. À un moment donné, il est très probable que le code se trouve dans un bloc try/catch. Il est peut-être vrai que le bloc try/catch serait plus difficile à intégrer et à construire un treillis approprié pour le résultat, mais la partie avec le réarrangement est ambiguë.

2 votes

Est-ce qu'un try...finally bloc sans catch empêche également certaines optimisations ?

6 votes

@Patashu "C'est en fait le fait de lancer l'exception qui vous coûte". Techniquement, en lançant l'exception n'est pas coûteuse ; l'instanciation de l'élément Exception est ce qui prend le plus de temps.

82voto

meriton Points 30447

Mesurons-le, d'accord ?

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1;
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("try") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        try {
                            x += i;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    return x;
                }
            }, new Benchmark("no try") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += i;
                    }
                    return x;
                }
            }
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

Sur mon ordinateur, cela donne quelque chose comme :

try     0.598 ns
no try  0.601 ns

Au moins dans cet exemple trivial, l'instruction try n'a eu aucun impact mesurable sur les performances. N'hésitez pas à en mesurer d'autres plus complexes.

D'une manière générale, je recommande de ne pas s'inquiéter du coût des performances des constructions du langage tant que vous n'avez pas la preuve d'un problème de performance réel dans votre code. Ou comme Donald Knuth mettre il : "l'optimisation prématurée est la racine de tous les maux".

8 votes

Alors que le try/no try est très probablement le même sur la plupart des JVM, le microbenchmark est terriblement défectueux.

2 votes

Plusieurs niveaux : vous voulez dire que les résultats sont calculés en moins de 1ns ? Le code compilé supprimera à la fois le try/catch ET la boucle complètement (la somme des nombres de 1 à n est une somme triviale de progression arithmétique). Même si le code contient des try/finally, le compilateur peut prouver qu'il n'y a rien à jeter là-dedans. Le code abstrait n'a que 2 sites d'appel et il sera cloné et inlined. Il y a d'autres cas, il suffit de consulter quelques articles sur les microbenchmarks et si vous décidez d'écrire un microbenchmark toujours vérifier l'assemblage généré.

3 votes

Les heures signalées sont par itération de la boucle. Comme une mesure ne sera utilisée que si elle a un temps total écoulé > 0,1 seconde (ou 2 milliards d'itérations, ce qui n'était pas le cas ici), je trouve votre affirmation selon laquelle la boucle a été supprimée dans son intégralité difficile à croire - car si la boucle a été supprimée, qu'est-ce qui a pris 0,1 seconde à exécuter ?

52voto

Evgeniy Dorofeev Points 52031

try / catch peut avoir un certain impact sur les performances. Cela est dû au fait qu'il empêche la JVM d'effectuer certaines optimisations. Joshua Bloch, dans "Effective Java", a déclaré ce qui suit :

- Placer du code à l'intérieur d'un bloc try-catch inhibe certaines optimisations que les implémentations modernes de la JVM pourraient autrement effectuer.

29 votes

"cela empêche la JVM de faire certaines optimisations"... ? Pouvez-vous nous donner plus de détails ?

5 votes

Le code à l'intérieur des blocs d'essai (habituellement ? toujours ?) ne peut pas être réorganisé avec le code à l'extérieur des blocs d'essai, par exemple.

3 votes

Notez que la question était de savoir si "c'est cher", et non si "cela a un impact sur les performances".

34voto

Hot Licks Points 25075

Yep, comme les autres l'ont dit, un try bloc inhibe certaines optimisations à travers l' {} personnages qui l'entourent. En particulier, l'optimiseur doit supposer qu'une exception peut se produire à n'importe quel point à l'intérieur du bloc, donc il n'y a aucune assurance que les instructions sont exécutées.

Par exemple:

    try {
        int x = a + b * c * d;
        other stuff;
    }
    catch (something) {
        ....
    }
    int y = a + b * c * d;
    use y somehow;

Sans l' try, la valeur calculée à attribuer à l' x a pu être sauvé comme un "sous-expression" et réutilisés pour attribuer à l' y. Mais à cause de l' try il n'y a aucune assurance que la première expression n'a jamais été évalué, de sorte que l'expression doit être recalculé. Ce n'est généralement pas un gros problème en "ligne droite" de code, mais peuvent être importants dans une boucle.

Il convient de noter, toutefois, que cela ne s'applique qu'à JITCed code. javac ne fait qu'un minable montant de l'optimisation, et il y a un coût nul pour l'interpréteur de bytecode pour entrer/quitter un try bloc. (Il n'y a pas bytecode généré pour marquer les limites des blocs.)

Et pour bestsss:

public class TryFinally {
    public static void main(String[] argv) throws Throwable {
        try {
            throw new Throwable();
        }
        finally {
            System.out.println("Finally!");
        }
    }
}

Sortie:

C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
        at TryFinally.main(TryFinally.java:4)

javap de sortie:

C:\JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
  public TryFinally();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":
()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Throwable;
    Code:
       0: new           #2                  // class java/lang/Throwable
       3: dup
       4: invokespecial #3                  // Method java/lang/Throwable."<init
>":()V
       7: athrow
       8: astore_1
       9: getstatic     #4                  // Field java/lang/System.out:Ljava/
io/PrintStream;
      12: ldc           #5                  // String Finally!
      14: invokevirtual #6                  // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
      17: aload_1
      18: athrow
    Exception table:
       from    to  target type
           0     9     8   any
}

Pas de "GOTO".

0 votes

Il n'y a pas de bytecodes générés pour marquer les limites des blocs. ce n'est pas nécessairement -- il faut un GOTO pour quitter le bloc, sinon il tombera dans le catch/finally cadre.

0 votes

@bestsss - Même si un GOTO est généré (ce qui n'est pas acquis), le coût de celui-ci est minuscule, et il est loin d'être un "marqueur" pour une limite de bloc -- les GOTO peuvent être générés pour de nombreuses constructions.

0 votes

Je n'ai jamais mentionné le coût, cependant il n'y a pas de bytecodes générés est une fausse déclaration. C'est tout. En fait, il n'y a pas de blocs dans le bytecode, les cadres ne sont pas des blocs.

8voto

technosaurus Points 1980

Pour comprendre pourquoi les optimisations ne peuvent pas être effectuées, il est utile de comprendre les mécanismes sous-jacents. L'exemple le plus succinct que j'ai pu trouver était implémenté dans des macros C à : http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)

Les compilateurs ont souvent des difficultés à déterminer si un saut peut être localisé en X, Y et Z, et ils sautent donc les optimisations dont ils ne peuvent garantir la sécurité, mais l'implémentation elle-même est plutôt légère.

4 votes

Les macros C que vous avez trouvées pour try/catch ne sont pas équivalentes à l'implémentation Java ou C#, qui émettent 0 instruction d'exécution.

0 votes

L'implémentation java est trop étendue pour être incluse dans son intégralité, il s'agit d'une implémentation simplifiée dans le but de comprendre l'idée de base de la façon dont les exceptions peuvent être implémentées. Dire qu'elle émet 0 instruction d'exécution est trompeur. Par exemple, une simple classcastexception étend runtimeexception qui étend exception qui étend throwable qui implique : grepcode.com/file/repository.grepcode.com/java/Root/jdk/openjdk/ ... C'est comme dire qu'un switch-case en C est gratuit si un seul cas est utilisé, il y a toujours une petite surcharge de démarrage.

0 votes

Les frais généraux de try est consommé au moment de la compilation, de la même manière qu'un compilateur consommera le coût de calcul de int a = 1 + 1; comme la constante 2 au moment de la compilation, plutôt qu'au moment de l'exécution. Il y a indéniablement une complexité, mais si aucune exception n'est levée, rien de supplémentaire n'est fait au moment de l'exécution.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X