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 de 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. c'est vraiment lent-même un ordre de grandeur plus lent que le code normal (les raisons données varient),

Et

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

Cette question concerne #1.

A titre d'exemple, cette page décrit la gestion des exceptions Java comme " très lente "et rapporte la lenteur à la création de la chaîne de message d'exception -" cette chaîne est ensuite utilisée pour créer 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 le lancement exceptions intrinsèquement lent". Une autre raison est que la génération de trace de pile est ce qui la ralentit.

Mes tests (en utilisant Java 1.6.0_07, Java HotSpot 10.0, sur Linux 32 bits), indiquent 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 si les return ou throw. 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 aient été erronés, mais je n'ai rien vu de l'exemple de code, des comparaisons de tests ou des résultats au cours de la dernière année ou deux qui montrent que la gestion des exceptions en Java est lente.

Ce qui me conduit dans ce chemin était un API j'avais besoin d'utiliser ces exceptions jetées dans le cadre de la logique de contrôle normale. Je voulais les corriger dans leur utilisation, mais maintenant je ne peux pas le faire. Devrai - je plutôt les féliciter de leur pensée 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.

413
demandé sur KIN 2008-11-18 18:31:11

17 réponses

Cela dépend de la façon dont les exceptions sont implémentées. Le moyen le plus simple est d'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é, la sortie du bloc try ne prend aucun 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 très rapide (en fait, rien ne se passe 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 il fait jeter pas tout à fait aussi lent. Je ne sais pas quelle JVM utilise cette nouvelle, meilleure technique...

...mais écrivez-vous en Java pour que votre code ne s'exécute plus tard que sur une JVM sur un système spécifique? Depuis si elle peut jamais fonctionner sur une autre plate-forme ou toute autre version de JVM (peut-être de tout autre fournisseur), qui a 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. Tu veux rester portable? Ensuite, ne pas comptez sur les exceptions étant rapides.

Cela fait aussi une grande différence ce que vous faites dans un bloc d'essai. 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 de sauvegarder l'état de la pile ni de dérouler la pile si une exception est levée (il suffit de sauter aux gestionnaires de catch). 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, cela 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 de programme. Pour à peu près n'importe quoi d'autre 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 d'essai 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 petit pour exclure des facteurs de confusion tels que les processus d'arrière-plan. Mais le bloc de capture 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 throw all 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
répondu 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 ceux de 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 le coût de lancer une exception est le temps passé à collecter la trace de la pile, ce 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
répondu Hot Licks 2011-11-05 23:14:35

Aleksey Shipilëv a fait une analyse très approfondie 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 repensé à chaque niveau vs enchaîné / enveloppé à chaque niveau
  • différents niveaux de profondeur de pile D'appels Java
  • pas d'optimisations d'inline vs extreme inlining vs paramètres par défaut
  • champs définis par L'utilisateur lus ou 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é textuellement de son post) étaient:

  1. Les exceptions vraiment exceptionnelles sont magnifiquement performantes. Si vous les utilisez comme conçus, et ne communiquez que les cas vraiment exceptionnels parmi le très grand nombre de cas non exceptionnels géré par le code normal, puis en utilisant des exceptions est la performance win.

  2. Les coûts de performance des exceptions ont deux composants principaux: stack trace construction lorsque L'Exception est instanciée et stack unwinding pendant le lancement de L'Exception.

  3. Les coûts de construction de trace de pile sont proportionnels à la profondeur de pile au moment de l'instanciation d'exception. C'est déjà mauvais parce que qui sur Terre connaît la profondeur de la pile à laquelle cela méthode de lancer serait appelé? Même si vous désactivez la génération de trace 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. Faut-il éliminer les deux effets, la performance le coût des exceptions est celui de la succursale locale. aussi beau que cela puisse paraître, 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 lever l'exception réelle.

  6. La règle optimiste de base semble être 10^-4 fréquence pour les exceptions sont assez exceptionnelles. Cela, bien sûr, dépend des poids lourds 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 d'utiliser un if à chaque fois. Le poste complet vaut beaucoup une lecture.

47
répondu Doval 2018-07-30 19:45:36

Ma réponse, malheureusement, est trop longue pour être postée ici. Alors laissez-moi résumer ici et 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" défaillances signalées comme exceptions " sont-elles lentes par rapport aux défaillances signalées d'autres façons?" 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 retourner une classe en cas de succès et une autre en cas d'échec. Vous pouvez penser à cela presque comme renvoyer 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 lancers 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. Dans la pratique, le facteur est beaucoup plus faible puisque nos méthodes qui peuvent é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ésultat, d'autre part, ne sacrifient pas du tout la sécurité de type. Ils enveloppent le succès et l'échec informations dans une seule classe. Donc, au lieu de "instanceof", ils fournissent un "isSuccess ()" et des getters pour les objets success et failure. Cependant, les objets de résultat sont à peu près 2x plus lents que d'utiliser des exceptions. Il s'avère que créer un nouvel objet wrapper à chaque fois est beaucoup plus cher 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 de juste le API quelles méthodes devraient toujours (principalement) fonctionner et lesquelles devraient signaler un échec.

Les Exceptions sont plus sûres que les Sentinelles, plus rapides que les objets de résultat 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 un é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 inutile 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
répondu 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, 8GB 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 toujours plus rapides, comparées aux méthodes lançant 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 qui lève les exceptions sans stacktrace sont 4-5 fois plus rapides que les Exceptions normales.

Modifier: Notacktracethrowable.java Merci @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}
16
répondu 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 la 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é). J'ai donc découvert que l'appel Thread.currentThread().getStackTrace() est extrêmement lent (en raison de la méthode native dumpThreads qu'il utilise en interne).

Donc Java Throwable, en conséquence, a une méthode native fillInStackTrace. Je pense que l' killer - catch bloc 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 sont compilées en 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
répondu 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'objets. 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 de John pour cela 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 contrôler le flux. Surtout pas pour des raisons de performances! Comme vous le mentionnez vous-même dans #2, vous risquez de masquer de graves bugs 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 dans le 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 appelé par un composant JSF déployé par JBoss. Maintenant, vous pourriez avoir une trace de pile qui fait plusieurs pages.

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

7
répondu Lars Westergren 2014-08-20 12:10:30

J'ai fait quelques tests de performance 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 plus que triplé (3x) avec des exceptions. Une boucle trivialement petite qui a dû attraper l'exception a vu une augmentation de 2X dans le temps de soi.

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

Les Exceptions doivent être définies non être utilisé pour tout ce qui est appelé fréquemment. Jetant un milliers des exceptions une seconde causerait un énorme goulot de bouteille.

Par exemple, en utilisant " Integer.ParseInt(...)" pour trouver toutes les mauvaises valeurs dans un fichier texte très volumineux, 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 GUI 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, alors utilisez un valeur de retour. Si c'est anormal, utilisez une exception. Par exemple: lecture des entrées utilisateur, les mauvaises valeurs sont normales-utilisez un code d'erreur. En passant une valeur à une fonction utilitaire interne, les mauvaises valeurs doivent être filtrées en appelant le code-utilisez une exception.

6
répondu 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 de 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 rationnelle et appelle parseInt () uniquement si la correspondance réussit. J'ai utilisé la regex de la manière la plus efficace que je pouvais (c'est-à-dire, créer les objets Pattern et Matcher avant d'intering la boucle), 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 rationnelle était deux fois plus rapide que parseInt (). Et si 20% étaient valides, ce qui signifie que l'exception a été levée et Attrapée 80% du temps, l'expression rationnelle était environ vingt fois plus rapide que parseInt().

J'ai été surpris par le résultat, considérant 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 plus que compensé pour 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 de l'utilisateur ou quelque chose comme ça, utilisez l'approche parseInt ().

6
répondu 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 le 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. J'imagine que la sagesse "conventionnelle" de lancer des exceptions étant lente était vraie dans les versions java antérieures (

3
répondu 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
répondu 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 D'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. La bibliothèque réalisateur peut avoir à écrire et à maintenir deux versions de l'Api, et l'appelant doit décider laquelle des deux versions à chaque situation.

C'est un peu un gâchis. 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... comme une installation 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 de valeur de retour d'erreur.

Classe publique 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'appel de 8, la version de vérification de la valeur d'erreur-retour était environ 27% plus lente que la version basline qui ne vérifiait pas les valeurs de retour.

Les performances D'Exception, en comparaison, ne sont pas fonction de la profondeur d'appel, mais de la fréquence d'exception. Cependant, la dégrédation à mesure que la fréquence des exceptions augmente est beaucoup plus dramatique. À seulement une fréquence d'erreur de 25%, le code a couru 24 fois plus lentement. À une fréquence d'erreur de 100%, la version d'exception est presque 100 fois plus lente.

Cela me suggère que peut-être font les mauvais compromis dans nos implémentations d'exception. 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 bloqués en les évitant quand nous voulons que notre code fonctionne vite.

3
répondu David Jeske 2012-03-18 22:01:15

Il suffit de comparer disons entier.parseInt à la méthode suivante, qui renvoie simplement une valeur par défaut dans le cas de données non utilisables 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 aux données "valides", elles fonctionneront toutes les deux à peu près au même rythme (même si entier.parseInt gère 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
répondu 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 courses, 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
répondu inder 2011-11-08 17:55:39

Un excellent article sur les performances des exceptions est:

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

Instanciation vs réutilisation existante, 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) lire le billet de blog original.

Cela signifie que Hibernate / Spring / etc-EE-shit est lent à cause des exceptions (xD) et de la réécriture du contrôle de l'application loin des exceptions (remplacez-le par continure / break et retourner boolean drapeaux comme dans C 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
répondu 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 de chaîne à valeur (scanner / analyseur), des bibliothèques respectées et bien connues aussi ;)

A Généralement la forme

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

Le nom de L'Exception est seulement un exemple, est généralement décoché (runtime), donc la déclaration throws est seulement mon image

Existe parfois deuxième forme:

public static Example Parse(String input, Example defaultValue)

Ne jamais lancer

Lorsque le second ins n'est pas disponible (ou que le programmeur lit trop moins de documents et utilisez seulement en premier), é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 de coût d'exceptions. Mais a un coût très élevé comparable des expressions régulières toujours par rapport à 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 après vos conférences assez rapide.

N'ayez pas peur Exceptions

-3
répondu 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 catch-block 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 je suis impatient de plus amples informations sur ce sujet!

-5
répondu qualbeen 2013-09-28 12:48:18