Quelle est la lenteur des exceptions Java?


Question: La gestion des exceptions en Java est-elle réellement lente?

La sagesse conventionnelle, ainsi que beaucoup de résultats Google, dit que la logique exceptionnelle ne devrait pas être utilisée pour le flux de programme normal en Java. Deux raisons sont généralement données,

  1. il est vraiment lent-même un ordre de grandeur plus lent que le code régulier (les raisons données varient),

Et

  1. c'est compliqué car les gens s'attendent à ce que seules les erreurs soient traitées dans un code exceptionnel.

Cette question concerne #1.

Par exemple, cette page décrit la gestion des exceptions Java comme "très lente" et relie la lenteur à la création de la chaîne de message d'exception - "cette chaîne est ensuite utilisée dans la création de l'objet d'exception qui est lancé. Ce n'est pas rapide."L'article Gestion efficace des exceptions en Java dit que "la raison en est due à l'aspect création d'objet de la gestion des exceptions, ce qui rend ainsi le lancement exceptions intrinsèquement lentes". Une autre raison est que la génération de traces de pile est ce qui la ralentit.

Mon test (en utilisant Java 1.6.0_07, Java HotSpot 10.0, sur Linux 32 bits), indique que la gestion des exceptions n'est pas plus lente que le code normal. J'ai essayé d'exécuter une méthode dans une boucle qui exécute du code. À la fin de la méthode, j'utilise un booléen pour indiquer s'il faut retourner ou jeter. De cette façon, le traitement est le même. J'ai essayé de lancer le méthodes dans différents ordres et moyenne de mes temps de test, pensant que c'était peut-être le réchauffement de la JVM. Dans tous mes tests, le lancer était au moins aussi rapide que le retour, sinon plus rapide (jusqu'à 3.1% plus rapide). Je suis complètement ouvert à la possibilité que mes tests étaient faux, mais je n'ai rien vu là-bas de la manière de l'échantillon de code, des comparaisons de test ou des résultats dans la dernière année ou deux qui montrent que la gestion des exceptions en Java est réellement lente.

Ce qui me conduit sur ce chemin était un API J'avais besoin d'utiliser qui a jeté des exceptions dans le cadre de la logique de contrôle normale. Je voulais les corriger dans leur utilisation, mais maintenant je ne pourrai peut-être pas. Devrai - je plutôt les féliciter pour leur vision avant-gardiste?

Dans l'article Gestion efficace des exceptions Java dans la compilation juste-à-temps, les auteurs suggèrent que la présence de gestionnaires d'exceptions seuls, même si aucune exception n'est levée, est suffisante pour empêcher le compilateur JIT d'optimiser correctement le code, le ralentissant ainsi vers le bas. Je n'ai pas encore testé cette théorie.

Author: KIN, 2008-11-18

17 answers

Cela dépend de la façon dont les exceptions sont implémentées. Le moyen le plus simple consiste à utiliser setjmp et longjmp. Cela signifie que tous les registres de la CPU sont écrits dans la pile (ce qui prend déjà un certain temps) et que d'autres données doivent éventuellement être créées... tout cela se produit déjà dans la déclaration try. L'instruction throw doit dérouler la pile et restaurer les valeurs de tous les registres (et d'autres valeurs possibles dans la machine virtuelle). Donc, essayez et jetez sont tout aussi lents, et c'est assez lent, mais si aucune exception est lancé, quitter le bloc try ne prend pas de temps dans la plupart des cas (car tout est mis sur la pile qui se nettoie automatiquement si la méthode existe).

Sun et d'autres ont reconnu que cela était peut-être sous-optimal et que les machines virtuelles devenaient de plus en plus rapides au fil du temps. Il existe une autre façon d'implémenter des exceptions, ce qui rend try lui-même extrêmement rapide (en fait, rien ne se passe du tout pour try en général-tout ce qui doit arriver est déjà fait lorsque la classe est chargée par la VM) et cela rend throw pas aussi lent. Je ne sais pas quelle JVM utilise cette nouvelle et meilleure technique...

...mais écrivez-vous en Java pour que votre code ne s'exécute plus que sur une JVM sur un système spécifique? Étant donné que s'il peut jamais fonctionner sur une autre plate-forme ou toute autre version JVM (éventuellement de tout autre fournisseur), qui dit qu'ils utilisent également l'implémentation rapide? Le rapide est plus compliqué que le lent et pas facilement possible sur tous les systèmes. Vous voulez rester portable? Ensuite, ne pas comptez sur les exceptions étant rapide.

Cela fait également une grande différence ce que vous faites dans un bloc try. Si vous ouvrez un bloc try et n'appelez jamais de méthode à partir de ce bloc try, le bloc try sera ultra rapide, car le JIT peut alors traiter un lancer comme un simple goto. Il n'a pas besoin d'enregistrer l'état de la pile ni de dérouler la pile si une exception est levée (il suffit de sauter aux gestionnaires de capture). Cependant, ce n'est pas ce que vous faites habituellement. Habituellement vous ouvrez un bloc d'essai et puis appelez une méthode qui pourrait lancer une exception, non? Et même si vous utilisez simplement le bloc try dans votre méthode, quel type de méthode sera-t-il, qui n'appelle aucune autre méthode? Va-t-il simplement calculer un nombre? Alors pour quoi avez-vous besoin d'exceptions? Il existe des moyens beaucoup plus élégants de réguler le flux du programme. Pour à peu près autre chose que des mathématiques simples, vous devrez appeler une méthode externe et cela détruit déjà l'avantage d'un bloc d'essai local.

Voir le code de test suivant:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

Résultat:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

Le ralentissement du bloc try est trop faible pour exclure des facteurs de confusion tels que les processus d'arrière-plan. Mais le bloc catch a tout tué et l'a rendu 66 fois plus lent!

Comme je l'ai dit, le résultat ne sera pas si mauvais si vous mettez try/catch et jetez tout dans la même méthode (method3), mais c'est une optimisation JIT spéciale sur laquelle je ne compterais pas. Et même en utilisant cette optimisation, le lancer est encore assez lent. Si Je ne sais pas ce que vous essayez de faire ici, mais il y a certainement une meilleure façon de le faire que d'utiliser try/catch/jeter.

 308
Author: Mecki, 2015-06-17 10:31:43

POUR info, j'ai étendu l'expérience que Mecki a faite:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

Les 3 premiers sont les mêmes que Mecki (mon ordinateur portable est évidemment plus lent).

Method4 est identique à method3 sauf qu'il crée un new Integer(1) plutôt que throw new Exception().

Method5 est comme method3 sauf qu'il crée le new Exception(), sans le jeter.

Method6 est comme method3 sauf qu'il lève une exception pré-créée (une variable d'instance) plutôt que d'en créer une nouvelle.

En Java une grande partie du la dépense pour lancer une exception est le temps passé à collecter la trace de pile, qui se produit lorsque l'objet exception est créé. Le coût réel de lancer l'exception, bien qu'important, est considérablement inférieur au coût de création de l'exception.

 220
Author: Hot Licks, 2011-11-05 23:14:35

Aleksey Shipilëv a fait une analyse très approfondie {[3] } dans laquelle il compare les exceptions Java sous diverses combinaisons de conditions:

  • Exceptions nouvellement créées vs exceptions pré-créées
  • Trace de pile activée vs désactivée
  • Trace de pile demandée vs jamais demandée
  • Pris au niveau supérieur vs rethrown à chaque niveau vs enchaîné / enveloppé à chaque niveau
  • Différents niveaux de profondeur de la pile d'appels Java
  • Pas d'optimisations d'inlining vs extreme inlining vs paramètres par défaut
  • Champs définis par l'utilisateur lus vs non lus

Il compare aussi à l'exécution de la vérification d'un code d'erreur à différents niveaux de la fréquence d'erreur.

Les conclusions (citées textuellement dans son article) étaient les suivantes:

  1. Les exceptions vraiment exceptionnelles sont magnifiquement performantes. Si vous les utilisez comme prévu, et que vous ne communiquez que les cas vraiment exceptionnels parmi le très grand nombre de cas non exceptionnels géré par du code régulier, puis en utilisant des exceptions est le gain de performance.

  2. Les coûts de performance des exceptions ont deux composants principaux: construction de trace de pile lorsque l'exception est instanciée etdéroulement de la pile lors de la levée de l'exception.

  3. Les coûts de construction des traces de pile sont proportionnels à la profondeur de pile au moment de l'instanciation de l'exception. C'est déjà mauvais parce que qui sur Terre connaît la profondeur de la pile à laquelle cela la méthode de lancer serait appelée? Même si vous désactivez la génération de traces de pile et / ou mettez en cache les exceptions, vous ne pouvez vous débarrasser que de cette partie du coût des performances.

  4. Les coûts de déroulement de la pile dépendent de la chance que nous avons de rapprocher le gestionnaire d'exceptions dans le code compilé. Structurer soigneusement le code pour éviter la recherche approfondie des gestionnaires d'exceptions nous aide probablement à avoir plus de chance.

  5. Devrions-nous éliminer les deux effets, la performance le coût des exceptions est celui de la succursale locale. Peu importe à quel point cela semble beau, cela ne signifie pas que vous devez utiliser des exceptions comme flux de contrôle habituel, car dans ce cas vous êtes à la merci de l'optimisation du compilateur!Vous ne devriez les utiliser que dans des cas vraiment exceptionnels, où la fréquence d'exception amortit le coût malchanceux possible de la levée de l'exception réelle.

  6. La règle optimiste semble être 10^-4 fréquence pour exceptions est assez exceptionnel. Cela, bien sûr, dépend du poids des exceptions elles-mêmes, des actions exactes prises dans les gestionnaires d'exceptions, etc.

Le résultat est que lorsqu'une exception n'est pas levée, vous ne payez pas de coût, donc lorsque la condition exceptionnelle est suffisamment rare, la gestion des exceptions est plus rapide que l'utilisation d'un if à chaque fois. Le post complet vaut vraiment la peine d'être lu.

 47
Author: Doval, 2018-07-30 19:45:36

Ma réponse, malheureusement, est trop longue à poster ici. Permettez-moi donc de résumer ici et de vous référer à http://www.fuwjax.com/how-slow-are-java-exceptions/ pour les détails graveleux.

La vraie question ici n'est pas "À quel point les" échecs signalés comme exceptions "sont-ils lents par rapport au "code qui n'échoue jamais"?"comme la réponse acceptée pourrait vous faire croire. Au lieu de cela, la question devrait être "À quel point les" échecs signalés comme exceptions " sont-ils lents par rapport aux échecs signalés d'autres manières?" Généralement, les deux autres façons de signaler les échecs sont soit avec des valeurs sentinelles, soit avec des wrappers de résultats.

Les valeurs sentinelles sont une tentative de renvoyer une classe en cas de succès et une autre en cas d'échec. Vous pouvez le considérer presque comme renvoyant une exception au lieu d'en lancer une. Cela nécessite une classe parent partagée avec l'objet success, puis une vérification "instanceof" et quelques moulages pour obtenir les informations de succès ou d'échec.

, Il s'avère qu'à le risque de sécurité de type, les valeurs Sentinelles sont plus rapides que les exceptions, mais seulement par un facteur d'environ 2x. Maintenant, cela peut sembler beaucoup, mais ce 2x ne couvre que le coût de la différence d'implémentation. En pratique, le facteur est beaucoup plus faible car nos méthodes qui pourraient échouer sont beaucoup plus intéressantes que quelques opérateurs arithmétiques comme dans l'exemple de code ailleurs dans cette page.

Les Wrappers de résultats, en revanche, ne sacrifient pas du tout la sécurité de type. Ils enveloppent le succès et l'échec informations dans une seule classe. Ainsi, au lieu de" instanceof", ils fournissent un" isSuccess () " et des getters pour les objets success et failure. Cependant, les objets de résultat sont environ 2x plus lents que d'utiliser des exceptions. Il s'avère que créer un nouvel objet wrapper à chaque fois est beaucoup plus coûteux que de lancer une exception parfois.

En plus de cela, les exceptions sont la langue fournie pour indiquer qu'une méthode peut échouer. Il n'y a pas d'autre moyen de dire à partir de seulement l' API quelles méthodes sont censées toujours (la plupart du temps) fonctionner et lesquelles sont censées signaler un échec.

Les exceptions sont plus sûres que les sentinelles, plus rapides que les objets result et moins surprenantes que les deux. Je ne suggère pas que try / catch remplace if / else, mais les exceptions sont la bonne façon de signaler l'échec, même dans la logique métier.

Cela dit, je voudrais souligner que les deux façons les plus fréquentes d'avoir un impact substantiel sur les performances que j'ai rencontrées créent des effets inutiles objets et boucles imbriquées. Si vous avez le choix entre créer une exception ou ne pas créer d'exception, ne créez pas l'exception. Si vous avez le choix entre créer une exception parfois ou créer un autre objet tout le temps, créez l'exception.

 35
Author: Fuwjax, 2018-07-30 20:31:19

J'ai étendu les réponses données par @Mecki et @incarnate, sans remplissage de stacktrace pour Java.

Avec Java 7+, nous pouvons utiliser Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). Mais pour Java6, voir ma réponse à cette question

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

Sortie avec Java 1.6.0_45, sur Core i7, 8 Go de RAM:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

Ainsi, les méthodes qui renvoient des valeurs sont plus rapides que les méthodes qui lancent des exceptions. À mon humble avis, nous ne pouvons pas concevoir une API claire en utilisant simplement des types de retour pour les flux de réussite et d'erreur. Méthode ce qui lève des exceptions sans stacktrace est 4-5 fois plus rapide que les exceptions normales.

Edit: NoStackTraceThrowable.java Merci @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}
 16
Author: manikanta, 2018-08-29 05:03:47

Je ne sais pas si ces sujets se rapportent, mais j'ai une fois voulu implémenter une astuce en m'appuyant sur la trace de pile du thread actuel: je voulais découvrir le nom de la méthode, qui a déclenché l'instanciation dans la classe instanciée (yeap, l'idée est folle, je l'ai totalement abandonnée). J'ai donc découvert que l'appel à Thread.currentThread().getStackTrace()est extrêmement lent (en raison de la méthode nativedumpThreads qu'il utilise en interne).

Donc Java Throwable, en conséquence, a une méthode native fillInStackTrace. Je pense que l' killer - catch block décrit précédemment déclenche en quelque sorte l'exécution de cette méthode.

, Mais laissez-moi vous raconter une autre histoire...

Dans Scala, certaines fonctionnalités fonctionnelles sont compilées dans la JVM en utilisant ControlThrowable, qui étend Throwable et remplace son fillInStackTrace de la manière suivante:

override def fillInStackTrace(): Throwable = this

J'ai donc adapté le test ci-dessus (le nombre de cycles est diminué de dix, ma machine est un peu plus lente:):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

Donc, les résultats sont:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

, Vous voyez, la seule différence entre method3 et method4 est qu'ils jettent différents types d'exceptions. Yeap, method4 est encore plus lent que method1 et method2, mais la différence est beaucoup plus acceptable.

 7
Author: incarnate, 2010-10-20 16:30:21

Je pense que le premier article fait référence à l'acte de traverser la pile d'appels et de créer une trace de pile comme étant la partie coûteuse, et bien que le deuxième article ne le dise pas, je pense que c'est la partie la plus chère de la création d'objet. John Rose a un article où il décrit différentes techniques pour accélérer les exceptions. (Préallocation et réutilisation d'une exception, exceptions sans traces de pile, etc.)

Mais encore - je pense que cela devrait être considéré comme un nécessaire le mal, un dernier recours. La raison pour laquelle John le fait est d'émuler des fonctionnalités dans d'autres langages qui ne sont pas (encore) disponibles dans la JVM. Vous ne devriez PAS prendre l'habitude d'utiliser des exceptions pour le flux de contrôle. Surtout pas pour des raisons de performances! Comme vous le mentionnez vous-même dans #2, vous risquez de masquer de graves bogues dans votre code de cette façon, et il sera plus difficile à maintenir pour les nouveaux programmeurs.

Les Microbenchmarks en Java sont étonnamment difficiles à obtenir (on m'a dit), surtout quand vous obtenez en territoire JIT, donc je doute vraiment que l'utilisation d'exceptions soit plus rapide que le "retour" dans la vie réelle. Par exemple, je soupçonne que vous avez quelque part entre 2 et 5 images de pile dans votre test? Imaginez maintenant que votre code sera invoqué par un composant JSF déployé par JBoss. Maintenant, vous pourriez avoir une trace de pile de plusieurs pages.

Peut-être pourriez-vous publier votre code de test?

 7
Author: Lars Westergren, 2014-08-20 12:10:30

J'ai fait des tests de performances avec JVM 1.5 et l'utilisation d'exceptions était au moins 2 fois plus lente. En moyenne: Le temps d'exécution sur une méthode trivialement petite a plus que triplé (3x) avec des exceptions. Une boucle trivialement petite qui devait attraper l'exception a vu une augmentation de 2x du temps de soi.

J'ai vu des nombres similaires dans le code de production ainsi que des micro benchmarks.

Les exceptions devraient PAS être utilisé pour tout ce qui est appelé fréquemment. Jetant un milliers des exceptions une seconde provoquerait un énorme goulot de bouteille.

Par exemple, en utilisant "Integer.ParseInt(...) "trouver toutes les mauvaises valeurs dans un très gros fichier texte--très mauvaise idée. (J'ai vu cette méthode utilitaire kill performance sur le code de production)

Utiliser une exception pour signaler une mauvaise valeur sur un formulaire d'interface utilisateur, probablement pas si mal du point de vue des performances.

Que ce soit ou non une bonne pratique de conception, j'irais avec la règle: si l'erreur est normale/attendue, utilisez un valeur de retour. Si c'est anormal, utilisez une exception. Par exemple: en lisant les entrées utilisateur, les mauvaises valeurs sont normales use utilisez un code d'erreur. En passant une valeur à une fonction utilitaire interne, les mauvaises valeurs doivent être filtrées en appelant le code use use an exception.

 6
Author: James Schek, 2008-11-18 16:41:13

Il y a quelque temps, j'ai écrit une classe pour tester les performances relatives de la conversion des chaînes en ints en utilisant deux approches: (1) call Integer.parseInt () et attrape l'exception, ou (2) correspond à la chaîne avec une expression régulière et n'appelle parseInt () que si la correspondance réussit. J'ai utilisé l'expression régulière de la manière la plus efficace possible (c'est-à-dire en créant les objets Pattern et Matcher avant d'intering the loop), et je n'ai pas imprimé ou enregistré les stacktraces des exceptions.

Pour une liste de dix mille chaînes, s'ils étaient tous des nombres valides, l'approche parseInt () était quatre fois plus rapide que l'approche regex. Mais si seulement 80% des chaînes étaient valides, l'expression régulière était deux fois plus rapide que parseInt(). Et si 20% étaient valides, ce qui signifie que l'exception a été levée et interceptée 80% du temps, l'expression régulière était environ vingt fois plus rapide que parseInt().

J'ai été surpris par le résultat, étant donné que l'approche regex traite deux fois les chaînes valides: une fois pour la correspondance et à nouveau pour parseInt(). Mais le fait de lancer et attraper des exceptions a plus que compensé cela. Ce genre de situation n'est pas susceptible de se produire très souvent dans le monde réel, mais si c'est le cas, vous ne devriez certainement pas utiliser la technique de capture d'exception. Mais si vous ne validez que l'entrée utilisateur ou quelque chose comme ça, utilisez par tous les moyens l'approche parseInt ().

 6
Author: Alan Moore, 2008-11-18 22:27:58

Même si lancer une exception n'est pas lent, c'est toujours une mauvaise idée de lancer des exceptions pour un flux de programme normal. Utilisé de cette façon, il est analogue à un GOTO...

Je suppose que cela ne répond pas vraiment à la question cependant. J'imagine que la sagesse "conventionnelle" de lancer des exceptions étant lente était vraie dans les versions antérieures de java (

 3
Author: user38051, 2008-11-18 15:41:29

HotSpot est tout à fait capable de supprimer le code d'exception pour les exceptions générées par le système, tant qu'il est tout en ligne. Cependant, les exceptions explicitement créées et celles qui ne sont pas supprimées passent beaucoup de temps à créer la trace de la pile. Remplacer fillInStackTrace pour voir comment cela peut affecter les performances.

 3
Author: Tom Hawtin - tackline, 2008-11-18 16:07:25

Les performances des exceptions en Java et C# laissent beaucoup à désirer.

En tant que programmeurs, cela nous oblige à vivre selon la règle "les exceptions devraient être causées rarement", simplement pour des raisons pratiques de performance.

Cependant, en tant qu'informaticiens, nous devrions nous rebeller contre cet état problématique. La personne qui crée une fonction n'a souvent aucune idée de la fréquence à laquelle elle sera appelée, ou si le succès ou l'échec est plus probable. Seul l'appelant a cette information. En essayant d'éviter les exceptions conduisent à des idoms API peu clairs où, dans certains cas, nous n'avons que des versions d'exception propres mais lentes, et dans d'autres cas, nous avons des erreurs de valeur de retour rapides mais maladroites, et dans d'autres cas encore, nous nous retrouvons avec les deux. L'implémenteur de bibliothèque peut avoir à écrire et à gérer deux versions d'API, et l'appelant doit décider laquelle des deux versions utiliser dans chaque situation.

C'est une sorte de désordre. Si les exceptions avaient de meilleures performances, nous pourrions éviter ces idiomes maladroits et utiliser des exceptions comme ils étaient destinés à être utilisés... en tant que facilité de retour d'erreur structurée.

J'aimerais vraiment voir des mécanismes d'exception implémentés en utilisant des techniques plus proches des valeurs de retour, afin que nous puissions avoir des performances plus proches des valeurs de retour.. puisque c'est ce à quoi nous revenons dans le code sensible aux performances.

Voici un exemple de code qui compare les performances d'exception aux performances d'erreur-retour-valeur.

Public class TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

Et voici les résultats:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

La vérification et la propagation des valeurs de retour ajoutent un coût par rapport à l'appel baseline-null, et ce coût est proportionnel à la profondeur d'appel. À une profondeur de chaîne d'appels de 8, la version de vérification de la valeur de retour d'erreur était environ 27% plus lente que la version basline qui ne vérifiait pas les valeurs de retour.

La performance des exceptions, en comparaison, n'est pas une fonction de la profondeur des appels, mais de la fréquence des exceptions. Cependant, la dégredation à mesure que la fréquence des exceptions augmente est beaucoup plus dramatique. À seulement une fréquence d'erreur de 25%, le code a fonctionné 24 FOIS plus lentement. Avec une fréquence d'erreur de 100%, la version d'exception est presque 100 FOIS plus lente.

Cela me suggère que nous faisons peut-être de mauvais compromis dans nos implémentations d'exceptions. Les exceptions pourraient être plus rapides, soit en évitant les promenades coûteuses, soit en les transformant carrément en vérification de la valeur de retour prise en charge par le compilateur. Jusqu'à ce qu'ils le fassent, nous sommes coincés à les éviter lorsque nous voulons que notre code s'exécute rapidement.

 3
Author: David Jeske, 2012-03-18 22:01:15

Il suffit de comparer disons Entier.analySez la méthode suivante, qui renvoie simplement une valeur par défaut dans le cas de données non analysables au lieu de lancer une exception:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

Tant que vous appliquez les deux méthodes à des données "valides", elles fonctionneront toutes les deux à peu près au même rythme (même si Entier.parseInt parvient à gérer des données plus complexes). Mais dès que vous essayez d'analyser des données invalides (par exemple pour analyser " abc " 1.000.000 fois), la différence de performance devrait être essentiel.

 2
Author: inflamer, 2011-01-04 17:26:57

J'ai changé la réponse de @Mecki ci-dessus pour que method1 renvoie un booléen et une vérification dans la méthode appelante, car vous ne pouvez pas simplement remplacer une exception par rien. Après deux essais, method1 était toujours la plus rapide ou aussi rapide que method2.

Voici un instantané du code:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

Et résultats:

Exécuter 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

Exécuter 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2
 0
Author: inder, 2011-11-08 17:55:39

Un excellent article sur les performances des exceptions est:

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

Instancier vs réutiliser existant, avec trace de pile et sans, etc:

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

En Fonction de la profondeur de la trace de la pile:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

Pour d'autres détails (y compris l'assembleur x64 de JIT), lisez l'article de blog original.

Cela signifie que Hibernate / Spring / etc-EE-shit sont lents à cause des exceptions (xD) et de la réécriture du flux de contrôle des applications loin des exceptions (remplacez-le par continure / break et en retournant boolean drapeaux comme en C à partir de l'appel de méthode) améliorer les performances de votre application 10x-100x, en fonction de la fréquence à laquelle vous les lancez ))

 0
Author: gavenkoa, 2018-03-21 19:38:56

Mon opinion sur la vitesse d'exception par rapport à la vérification des données par programme.

De nombreuses classes avaient un convertisseur chaîne / valeur (scanner / analyseur), des bibliothèques respectées et bien connues;)

A Généralement la forme

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

Le nom de l'exception n'est qu'un exemple, est généralement décoché (runtime), donc la déclaration est seulement mon image

Existe parfois deuxième forme:

public static Example Parse(String input, Example defaultValue)

Ne jamais lancer

Lorsque le deuxième ins n'est pas disponible (ou que le programmeur lit trop moins de documents et utilisez seulement d'abord), écrivez un tel code avec une expression régulière. Les expressions régulières sont cool, politiquement correctes, etc.:

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

Avec ce code, les programmeurs n'ont pas coûté d'exceptions. MAIS A un coût très ÉLEVÉ comparable des expressions régulières TOUJOURS contre un faible coût d'exception parfois.

J'utilise presque toujours dans un tel contexte

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

Sans analyser stacktrace etc., je crois qu'après vos conférences, vous êtes assez rapide.

N'ayez pas peur des exceptions

 -3
Author: Jacek Cz, 2015-09-12 18:00:04

Pourquoi les exceptions devraient-elles être plus lentes que les retours normaux?

Tant que vous n'imprimez pas la stacktrace sur le terminal, enregistrez-la dans un fichier ou quelque chose de similaire, le bloc catch ne fait pas plus de travail que les autres blocs de code. Donc, je ne peux pas imaginer pourquoi "throw new my_cool_error()" devrait être aussi lent.

Bonne question et j'attends avec impatience de plus amples informations sur ce sujet!

 -5
Author: qualbeen, 2013-09-28 12:48:18