web-dev-qa-db-fra.com

L'utilisation de blocs try-catch est-elle coûteuse même si une exception n'est jamais levée?

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

J'ai trouvé la question/réponse Stool Overflow Pourquoi les blocs d'essai sont-ils coûteux?, mais c'est pour . NET .

177
jsedano

try n'a pratiquement aucune dépense. Au lieu d'effectuer le travail de configuration de try au moment de l'exécution, les métadonnées du code sont structurées au moment de la compilation de sorte que lorsqu'une exception est générée, il effectue maintenant une opération relativement coûteuse consistant à remonter la pile et à voir si try il existe des blocs qui intercepteraient cette exception. Du point de vue d'un profane, try peut également être gratuit. En fait, c'est l'exception qui vous coûte - mais à moins que vous ne leviez des centaines ou des milliers d'exceptions, vous n'en remarquerez toujours pas le coût.


try comporte des coûts mineurs. Java) ne peut pas effectuer certaines optimisations sur le code d'un bloc try comme il le ferait autrement. Par exemple, Java modifiera souvent les instructions dans 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'écrites dans le code source, étaient exécutées commande jusqu'à une ligne.

Parce que dans un bloc try, une exception peut être levée (à n’importe quelle ligne du bloc try! Certaines exceptions sont levées de manière asynchrone, par exemple en appelant stop sur un thread (qui est obsolète), et Même si OutOfMemoryError peut se produire presque n'importe où) et pourtant, il peut être intercepté et le code continue à s'exécuter par la suite de la même manière, il est plus difficile de raisonner sur les optimisations pouvant être effectuées, elles sont donc moins susceptibles de se produire. (Quelqu'un devrait programmer le compilateur pour le faire, raisonner et garantir l'exactitude, etc. Ce serait très pénible pour quelque chose considéré comme 'exceptionnel') Mais encore une fois, dans la pratique, vous ne remarquerez rien de tel.

185
Patashu

Allons le mesurer, allons-nous?

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 affiche 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. Ne hésitez pas à mesurer les plus complexes.

De manière générale, je vous recommande de ne pas vous soucier du coût de performance des constructions de langage tant que vous ne disposez pas de la preuve d'un problème de performance réel dans votre code. Ou comme Donald Knuth mettre le: "l'optimisation prématurée est la racine de tout le mal".

66
meriton

try/catch peut avoir un impact sur les performances. En effet, cela empêche la JVM d'effectuer certaines optimisations. Joshua Bloch, dans "Effective Java", a déclaré ce qui suit:

• Le fait d'insérer du code dans un bloc try-catch empêche certaines optimisations que les implémentations modernes de JVM pourraient autrement effectuer.

42

Oui, comme l'ont dit les autres, un bloc try inhibe certaines optimisations sur le {} caractères qui l’entourent. En particulier, l'optimiseur doit supposer qu'une exception peut se produire à n'importe quel point du bloc. Par conséquent, rien ne garantit 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 try, la valeur calculée pour affecter à x pourrait être enregistrée en tant que "sous-expression commune" et réutilisée pour être affectée à y. Mais à cause de try, rien ne garantit que la première expression a déjà été évaluée. Cette expression doit donc être recalculée. Ce n'est pas généralement un gros problème dans le code "en ligne droite", mais peut être significatif dans une boucle.

Il convient toutefois de noter que cela s’applique UNIQUEMENT au code JITCed. javac n'effectue qu'une optimisation minime, et l'interpréteur de codes octets n'a aucun coût pour entrer/laisser un bloc try. (Aucun bytecode n'est généré pour marquer les limites du bloc.)

Et pour les meilleurs:

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)

sortie javap:

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.println:(Ljava/lang/String;)V
      17: aload_1
      18: athrow
    Exception table:
       from    to  target type
           0     9     8   any
}

Non "GOTO".

27
Hot Licks

Encore un autre micro-repère ( source ).

J'ai créé un test dans lequel je mesure la version du code try-catch et no-try-catch en fonction d'un pourcentage d'exception. 10% pourcentage signifie que 10% des cas tests ont été divisés par zéro cas. Dans un cas, il est géré par un bloc try-catch, dans un autre par un opérateur conditionnel. Voici mon tableau de résultats:

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
 Pourcentage | Résultat (try/if, ns) 
 0% | 88/90 
 1% | 89/87 
 10% | 86/97 
 90% | 85/83 

Ce qui dit qu'il n'y a pas de différence significative entre ces cas.

7
Andrey Chaschev

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 a été mis en œuvre dans les macros C à l'adresse suivante: 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 du mal à déterminer si un saut peut être localisé en X, Y et Z, alors ils sautent des optimisations qu’ils ne peuvent garantir de sécurité, mais la mise en œuvre elle-même est plutôt légère.

6
technosaurus

J'ai trouvé attraper NullPointException assez cher. Pour 1.2k opérations, le temps était de 200 ms et 12 ms lorsque je l’ai manipulé de la même façon avec if(object==null), ce qui était une amélioration pour moi.

2
Mateusz Kaflowski