279 votes

La partie de lancer une Exception est cher?

En Java, à l'aide de lancer/attraper comme une partie de la logique quand il n'y a pas en fait d'une erreur est généralement une mauvaise idée (en partie) parce que lancer et attraper une exception est cher, et de le faire de nombreuses fois dans une boucle est généralement beaucoup plus lent que les autres structures de contrôle qui n'impliquent pas de lever des exceptions.

Ma question est, est frais engagés dans le lancer/attraper lui-même, ou lors de la création de l'objet de l'Exception (car il reçoit beaucoup d'informations d'exécution, y compris la pile d'exécution)?

En d'autres termes, si je ne

Exception e = new Exception();

mais ne le jetez pas, c'est que la plupart des coûts de jeter, ou est le jet + prise le traitement ce qui est coûteux?

Je ne demande pas si de mettre le code dans un bloc try/catch ajoute le coût de l'exécution de ce code, je me demande si la capture de l'Exception est la partie coûteuse, ou de la création (en appelant le constructeur de) l'Exception est la partie coûteuse.

Une autre façon de poser, c'est que si j'ai fait une instance de l'Exception et la jeta et l'a attrapé, ce serait nettement plus rapide que de créer une nouvelle Exception à chaque fois que je le jeter?

316voto

apangin Points 4693

La création d' un objet d'exception n'est pas plus cher que d'en créer d'autres objets. Le coût principal est caché dans natif fillInStackTrace méthode qui marche à travers la pile d'appel et recueille toutes les informations nécessaires pour construire une trace de la pile: cours, méthode noms, les numéros de ligne etc.

Le mythe de la grande exception des coûts provient du fait que la plupart des Throwable constructeurs implicitement appel fillInStackTrace. Cependant, il existe un constructeur pour créer un Throwable sans une trace de la pile. Il vous permet de faire des throwables qui sont très rapides à instancier. Une autre façon de créer de légères exceptions consiste à substituer fillInStackTrace.


Maintenant que diriez-vous de jeter une exception?
En fait, cela dépend d'où la levée d'une exception est interceptée.

S'il est pris dans la même méthode (ou, plus précisément, dans le même contexte, car le contexte peut inclure plusieurs méthodes en raison de l'in-lining), alors throw est rapide et simple que goto (bien sûr, après la compilation JIT).

Toutefois, si un catch bloc est quelque part plus loin dans la pile, puis JVM besoins à la détente de la pile d'images, et cela peut prendre beaucoup plus de temps. C'est encore plus long, si il y a synchronized des blocs ou des méthodes de participer, en raison de déroulement implique la libération de moniteurs propriété retirée de la pile des images.


J'ai pu confirmer les déclarations ci-dessus par des indices de référence, mais heureusement je n'ai pas besoin à faire, puisque tous les aspects sont déjà parfaitement couverts dans le poste de HotSpot performance ingénieur Alexey Shipilëv: La Performance Exceptionnelle de Lil' Exception.

78voto

erickson Points 127945

La première opération dans la plupart des Throwable constructeurs est de remplir la trace de la pile, qui est où la plupart de la dépense.

Il est, cependant, un protégé constructeur avec un drapeau pour désactiver la trace de la pile. Ce constructeur est accessible lors de l'extension d' Exception . Si vous créez un type d'exception personnalisé, vous pouvez éviter la trace de la pile de création et d'obtenir de meilleures performances au détriment de moins d'informations.

Si vous créez une exception de type par des moyens normaux, vous pouvez re-jeter à de nombreuses reprises sans la surcharge de remplissage dans la trace de la pile. Cependant, sa trace de la pile va réfléchir à où il a été construit, non pas où il a été jeté dans un cas particulier.

Les versions actuelles de Java faire quelques tentatives pour optimiser la trace de la pile de la création. Code natif est appelée à remplir dans la trace de la pile, qui enregistre la trace dans un plus léger, la structure native. Java correspondante StackTraceElement des objets sont paresseusement créé à partir de cet enregistrement uniquement lorsque l' getStackTrace(), printStackTrace(), ou d'autres méthodes qui nécessitent la trace sont appelés.

Si vous éliminez la trace de la pile de la génération, de l'autre principal coût est dénouement de la pile entre le lancer et de le rattraper. Le moins d'intervention des cadres rencontrés avant, l'exception est interceptée, plus vite ce sera.

La conception de votre programme de sorte que les exceptions sont jetés seulement dans des cas vraiment exceptionnels, et des optimisations de ce genre sont difficiles à justifier.

28voto

Harry Points 7841

Theres une bonne écriture sur les Exceptions ici.

http://shipilev.net/blog/2014/exceptional-performance/

La conclusion étant que la trace de la pile de la construction et le déroulement de pile sont les pièces qui coûtent cher. Le code ci-dessous bénéficie d'une fonction en 1.7 où l'on peut tourner les traces de pile sur et en dehors. On peut alors l'utiliser pour voir ce genre de coûts des différents scénarios ont

Voici les horaires pour la création de l'Objet seul. J'ai ajouté String ici, donc vous pouvez voir que sans la pile étant écrit, il n'y a presque pas de différence dans la création d'un JavaException Objet et un String. Avec pile d'écriture activée, la différence est spectaculaire c'est à dire à moins d'un ordre de grandeur inférieur.

Time to create million String objects: 41.41 (ms)
Time to create million JavaException objects with    stack: 608.89 (ms)
Time to create million JavaException objects without stack: 43.50 (ms)

Le tableau suivant indique combien de temps il a fallu pour retourner à partir d'une projection à une profondeur d'un million de fois.

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|           1428|             243| 588 (%)|
|   15|           1763|             393| 449 (%)|
|   14|           1746|             390| 448 (%)|
|   13|           1703|             384| 443 (%)|
|   12|           1697|             391| 434 (%)|
|   11|           1707|             410| 416 (%)|
|   10|           1226|             197| 622 (%)|
|    9|           1242|             206| 603 (%)|
|    8|           1251|             207| 604 (%)|
|    7|           1213|             208| 583 (%)|
|    6|           1164|             206| 565 (%)|
|    5|           1134|             205| 553 (%)|
|    4|           1106|             203| 545 (%)|
|    3|           1043|             192| 543 (%)| 

La suite est presque certainement une brute sur la simplification...

Si nous prenons une profondeur de 16 ans avec pile de l'écriture puis de la création de l'objet est de l'ordre de ~40% du temps, la trace de la pile représente la grande majorité de ce. ~93% de l'instanciation de la JavaException objet est due à la trace de la pile d'être pris. Cela signifie que le dénouement de la pile dans ce cas est de prendre les autres 50% du temps.

Lorsque nous éteignons la trace de la pile d'objets de création de comptes pour un beaucoup plus petit fraction, soit 20% et le déroulement de pile représente aujourd'hui 80% du temps.

Dans les deux cas, le déroulement de pile prend une grande partie du temps global.

public class JavaException extends Exception {
  JavaException(String reason, int mode) {
    super(reason, null, false, false);
  }
  JavaException(String reason) {
    super(reason);
  }

  public static void main(String[] args) {
    int iterations = 1000000;
    long create_time_with    = 0;
    long create_time_without = 0;
    long create_string = 0;
    for (int i = 0; i < iterations; i++) {
      long start = System.nanoTime();
      JavaException jex = new JavaException("testing");
      long stop  =  System.nanoTime();
      create_time_with += stop - start;

      start = System.nanoTime();
      JavaException jex2 = new JavaException("testing", 1);
      stop = System.nanoTime();
      create_time_without += stop - start;

      start = System.nanoTime();
      String str = new String("testing");
      stop = System.nanoTime();
      create_string += stop - start;

    }
    double interval_with    = ((double)create_time_with)/1000000;
    double interval_without = ((double)create_time_without)/1000000;
    double interval_string  = ((double)create_string)/1000000;

    System.out.printf("Time to create %d String objects: %.2f (ms)\n", iterations, interval_string);
    System.out.printf("Time to create %d JavaException objects with    stack: %.2f (ms)\n", iterations, interval_with);
    System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)\n", iterations, interval_without);

    JavaException jex = new JavaException("testing");
    int depth = 14;
    int i = depth;
    double[] with_stack    = new double[20];
    double[] without_stack = new double[20];

    for(; i > 0 ; --i) {
      without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000;
      with_stack[i]    = jex.timerLoop(i, iterations, 1)/1000000;
    }
    i = depth;
    System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)|\n");
    for(; i > 0 ; --i) {
      double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
      System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| \n", i + 2, with_stack[i] , without_stack[i], ratio);
      //System.out.printf("%d\t%.2f (ms)\n", i, ratio);
    }
  }
 private int thrower(int i, int mode) throws JavaException {
    ExArg.time_start[i] = System.nanoTime();
    if(mode == 0) { throw new JavaException("without stack", 1); }
    throw new JavaException("with stack");
  }
  private int catcher1(int i, int mode) throws JavaException{
    return this.stack_of_calls(i, mode);
  }
  private long timerLoop(int depth, int iterations, int mode) {
    for (int i = 0; i < iterations; i++) {
      try {
        this.catcher1(depth, mode);
      } catch (JavaException e) {
        ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]);
      }
    }
    //long stop = System.nanoTime();
    return ExArg.time_accum[depth];
  }

  private int bad_method14(int i, int mode) throws JavaException  {
    if(i > 0) { this.thrower(i, mode); }
    return i;
  }
  private int bad_method13(int i, int mode) throws JavaException  {
    if(i == 13) { this.thrower(i, mode); }
    return bad_method14(i,mode);
  }
  private int bad_method12(int i, int mode) throws JavaException{
    if(i == 12) { this.thrower(i, mode); }
    return bad_method13(i,mode);
  }
  private int bad_method11(int i, int mode) throws JavaException{
    if(i == 11) { this.thrower(i, mode); }
    return bad_method12(i,mode);
  }
  private int bad_method10(int i, int mode) throws JavaException{
    if(i == 10) { this.thrower(i, mode); }
    return bad_method11(i,mode);
  }
  private int bad_method9(int i, int mode) throws JavaException{
    if(i == 9) { this.thrower(i, mode); }
    return bad_method10(i,mode);
  }
  private int bad_method8(int i, int mode) throws JavaException{
    if(i == 8) { this.thrower(i, mode); }
    return bad_method9(i,mode);
  }
  private int bad_method7(int i, int mode) throws JavaException{
    if(i == 7) { this.thrower(i, mode); }
    return bad_method8(i,mode);
  }
  private int bad_method6(int i, int mode) throws JavaException{
    if(i == 6) { this.thrower(i, mode); }
    return bad_method7(i,mode);
  }
  private int bad_method5(int i, int mode) throws JavaException{
    if(i == 5) { this.thrower(i, mode); }
    return bad_method6(i,mode);
  }
  private int bad_method4(int i, int mode) throws JavaException{
    if(i == 4) { this.thrower(i, mode); }
    return bad_method5(i,mode);
  }
  protected int bad_method3(int i, int mode) throws JavaException{
    if(i == 3) { this.thrower(i, mode); }
    return bad_method4(i,mode);
  }
  private int bad_method2(int i, int mode) throws JavaException{
    if(i == 2) { this.thrower(i, mode); }
    return bad_method3(i,mode);
  }
  private int bad_method1(int i, int mode) throws JavaException{
    if(i == 1) { this.thrower(i, mode); }
    return bad_method2(i,mode);
  }
  private int stack_of_calls(int i, int mode) throws JavaException{
    if(i == 0) { this.thrower(i, mode); }
    return bad_method1(i,mode);
  }
}

class ExArg {
  public static long[] time_start;
  public static long[] time_accum;
  static {
     time_start = new long[20];
     time_accum = new long[20];
  };
}

La pile d'images dans cet exemple sont minuscules par rapport à ce que vous trouveriez normalement.

Vous pouvez coup d'oeil au code binaire à l'aide de javap

javap -c -v -constants JavaException.class

ie c'est pour la méthode 4...

   protected int bad_method3(int, int) throws JavaException;
flags: ACC_PROTECTED
Code:
  stack=3, locals=3, args_size=3
     0: iload_1       
     1: iconst_3      
     2: if_icmpne     12
     5: aload_0       
     6: iload_1       
     7: iload_2       
     8: invokespecial #6                  // Method thrower:(II)I
    11: pop           
    12: aload_0       
    13: iload_1       
    14: iload_2       
    15: invokespecial #17                 // Method bad_method4:(II)I
    18: ireturn       
  LineNumberTable:
    line 63: 0
    line 64: 12
  StackMapTable: number_of_entries = 1
       frame_type = 12 /* same */

Exceptions:
  throws JavaException

16voto

Austin D Points 1968

La création de l' Exception avec un null trace de la pile dure environ autant de temps que l' throw et try-catch bloc ensemble. Cependant, le remplissage de la trace de la pile dure en moyenne 5 fois plus longtemps.

J'ai créé le banc de test suivant pour démontrer l'impact sur les performances. J'ai ajouté de l' -Djava.compiler=NONE de la course de Configuration pour désactiver l'optimisation du compilateur. Pour mesurer l'impact de la construction de la trace de la pile, j'ai prolongé l' Exception classe de prendre avantage de la pile-libre constructeur:

class NoStackException extends Exception{
    public NoStackException() {
        super("",null,false,false);
    }
}

L'indice de référence code est comme suit:

public class ExceptionBenchmark {

    private static final int NUM_TRIES = 100000;

    public static void main(String[] args) {

        long throwCatchTime = 0, newExceptionTime = 0, newObjectTime = 0, noStackExceptionTime = 0;

        for (int i = 0; i < 30; i++) {
            throwCatchTime += throwCatchLoop();
            newExceptionTime += newExceptionLoop();
            newObjectTime += newObjectLoop();
            noStackExceptionTime += newNoStackExceptionLoop();
        }

        System.out.println("throwCatchTime = " + throwCatchTime / 30);
        System.out.println("newExceptionTime = " + newExceptionTime / 30);
        System.out.println("newStringTime = " + newObjectTime / 30);
        System.out.println("noStackExceptionTime = " + noStackExceptionTime / 30);

    }

    private static long throwCatchLoop() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {

                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newObjectLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new Object();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newNoStackExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            NoStackException e = new NoStackException();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

}

Sortie:

throwCatchTime = 19
newExceptionTime = 77
newObjectTime = 3
noStackExceptionTime = 15

Cela implique que la création d'un NoStackException est environ aussi cher qu'à plusieurs reprises en jetant les mêmes Exception. Il montre également que la création de l' Exception et le remplissage de sa trace de la pile dure environ 4x plus longtemps.

4voto

Harry Points 7841

Cette partie de la question...

Une autre façon de poser, c'est que si j'ai fait une instance de l'Exception et de la jeté et l'a attrapé, ce serait nettement plus rapide que de créer une nouvelle Exception à chaque fois que je le jeter?

Semble demander si la création d'une exception et la mise en cache quelque part améliore les performances. Oui, c'est fait. C'est la même chose que de tourner hors de la pile est écrit sur la création d'objet, parce que ça a déjà été fait.

Ce sont les timings, j'ai reçu, veuillez lire la mise en garde après cette...

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|            193|             251| 77 (%)| 
|   15|            390|             406| 96 (%)| 
|   14|            394|             401| 98 (%)| 
|   13|            381|             385| 99 (%)| 
|   12|            387|             370| 105 (%)| 
|   11|            368|             376| 98 (%)| 
|   10|            188|             192| 98 (%)| 
|    9|            193|             195| 99 (%)| 
|    8|            200|             188| 106 (%)| 
|    7|            187|             184| 102 (%)| 
|    6|            196|             200| 98 (%)| 
|    5|            197|             193| 102 (%)| 
|    4|            198|             190| 104 (%)| 
|    3|            193|             183| 105 (%)| 

Bien sûr, le problème avec ceci est votre trace de la pile maintenant les points à l'endroit où vous instancié l'objet n'est pas où on l'a jeté.

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