Quanto sono lente le eccezioni Java?


Domanda: la gestione delle eccezioni in Java è effettivamente lenta?

La saggezza convenzionale, così come molti risultati di Google, dice che la logica eccezionale non dovrebbe essere utilizzata per il normale flusso di programmi in Java. Di solito vengono date due ragioni,

  1. è molto lento-anche un ordine di grandezza più lento del normale codice (i motivi indicati variano),

E

  1. è disordinato perché le persone si aspettano che solo gli errori vengano gestiti in codice eccezionale.

Questa domanda riguarda il numero 1.

Ad esempio, questa pagina descrive la gestione delle eccezioni Java come "molto lenta" e mette in relazione la lentezza con la creazione della stringa del messaggio di eccezione - "questa stringa viene quindi utilizzata per creare l'oggetto di eccezione che viene generato. Questo non è veloce."L'articolo Gestione efficace delle eccezioni in Java dice che" la ragione di ciò è dovuta all'aspetto della creazione dell'oggetto della gestione delle eccezioni, che rende quindi il lancio eccezioni intrinsecamente lente". Un altro motivo là fuori è che la generazione della traccia dello stack è ciò che lo rallenta.

Il mio test (usando Java 1.6.0_07, Java HotSpot 10.0, su Linux a 32 bit) indica che la gestione delle eccezioni non è più lenta del normale codice. Ho provato a eseguire un metodo in un ciclo che esegue del codice. Alla fine del metodo, uso un booleano per indicare se restituire o lanciare . In questo modo l'elaborazione effettiva è la stessa. Ho provato a correre il metodi in diversi ordini e media dei miei tempi di test, pensando che potrebbe essere stato il riscaldamento della JVM. In tutti i miei test, il tiro era almeno veloce come il ritorno, se non più veloce (fino al 3.1% più veloce). Sono completamente aperto alla possibilità che i miei test fossero sbagliati, ma non ho visto nulla là fuori nel modo dell'esempio di codice, dei confronti di test o dei risultati nell'ultimo anno o due che mostrano che la gestione delle eccezioni in Java è effettivamente lenta.

Ciò che mi porta su questa strada è stato un API avevo bisogno di usare che ha gettato eccezioni come parte della normale logica di controllo. Volevo correggerli nel loro utilizzo, ma ora potrei non essere in grado di farlo. Dovrò invece lodarli per la loro lungimiranza?

Nel documento Gestione efficiente delle eccezioni Java nella compilazione just-in-time, gli autori suggeriscono che la presenza di gestori di eccezioni da soli, anche se non vengono generate eccezioni, è sufficiente per impedire al compilatore JIT di ottimizzare correttamente il codice, rallentando così giù. Non ho ancora testato questa teoria.

Author: KIN, 2008-11-18

17 answers

Dipende da come vengono implementate le eccezioni. Il modo più semplice è usare setjmp e longjmp. Ciò significa che tutti i registri della CPU sono scritti nello stack (che richiede già un po ' di tempo) e possibilmente alcuni altri dati devono essere creati... tutto ciò accade già nella dichiarazione try. L'istruzione throw deve srotolare lo stack e ripristinare i valori di tutti i registri (e possibili altri valori nella VM). Quindi prova a lanciare sono ugualmente lenti, e questo è piuttosto lento, tuttavia se non fa eccezione viene lanciato, l'uscita dal blocco try non richiede alcun tempo nella maggior parte dei casi (poiché tutto viene messo in pila che pulisce automaticamente se il metodo esiste).

Sun e altri hanno riconosciuto che questo è probabilmente non ottimale e, naturalmente, le macchine virtuali diventano sempre più veloci nel tempo. C'è un altro modo per implementare le eccezioni, il che rende try stesso fulmineo (in realtà non succede nulla per try in generale-tutto ciò che deve accadere è già fatto quando la classe viene caricata dalla VM) e rende il lancio non altrettanto lento. Non conosco quale JVM usi questa nuova tecnica migliore...

...ma stai scrivendo in Java in modo che il tuo codice in seguito funzioni solo su una JVM su un sistema specifico? Dal momento che se potrebbe mai funzionare su qualsiasi altra piattaforma o qualsiasi altra versione JVM (possibilmente di qualsiasi altro fornitore), chi dice che usa anche l'implementazione veloce? Quello veloce è più complicato di quello lento e non facilmente possibile su tutti i sistemi. Vuoi rimanere portatile? Allora non farlo fai affidamento sulle eccezioni che sono veloci.

Fa anche una grande differenza quello che fai all'interno di un blocco try. Se apri un blocco try e non chiami mai alcun metodo all'interno di questo blocco try, il blocco try sarà ultra veloce, poiché il JIT può effettivamente trattare un tiro come un semplice goto. Non ha né bisogno di salvare lo stack-state né ha bisogno di srotolare lo stack se viene generata un'eccezione (deve solo passare ai gestori di catch). Tuttavia, questo non è quello che fai di solito. Di solito si apre un blocco try e poi chiama un metodo che potrebbe generare un'eccezione, giusto? E anche se usi semplicemente il blocco try all'interno del tuo metodo, che tipo di metodo sarà questo, che non chiama nessun altro metodo? Sarà solo calcolare un numero? Allora per cosa hai bisogno di eccezioni? Ci sono modi molto più eleganti per regolare il flusso del programma. Per praticamente qualsiasi altra cosa, ma semplice matematica, dovrai chiamare un metodo esterno e questo già distrugge il vantaggio di un blocco try locale.

Cfr. seguente codice di prova:

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()
        );
    }
}

Risultato:

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

Il rallentamento dal blocco try è troppo piccolo per escludere fattori confondenti come i processi in background. Ma il blocco di cattura ha ucciso tutto e lo ha reso 66 volte più lento!

Come ho detto, il risultato non sarà poi così male se metti try/catch e lanci tutto nello stesso metodo (method3), ma questa è un'ottimizzazione JIT speciale su cui non farei affidamento. E anche quando si utilizza questa ottimizzazione, il tiro è ancora piuttosto lento. Cosi Non so cosa stai cercando di fare qui, ma c'è sicuramente un modo migliore di farlo che usare try/catch/throw.

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

CORDIALI saluti, ho esteso l'esperimento che Mecki ha fatto:

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

I primi 3 sono gli stessi di Mecki (il mio portatile è ovviamente più lento).

Method4 è identico a method3 tranne che crea un new Integer(1) piuttosto che fare throw new Exception().

Method5 è come method3 tranne che crea il new Exception() senza lanciarlo.

Method6 è come method3 tranne che genera un'eccezione pre-creata (una variabile di istanza) piuttosto che crearne una nuova.

In Java gran parte del la spesa per lanciare un'eccezione è il tempo trascorso a raccogliere la traccia dello stack, che si verifica quando viene creato l'oggetto exception. Il costo effettivo di lanciare l'eccezione, anche se grande, è notevolmente inferiore al costo di creare l'eccezione.

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

Aleksey Shipilëv ha fatto un'analisi molto approfondita{[3] } in cui mette a confronto le eccezioni Java in varie combinazioni di condizioni:

  • Eccezioni appena create vs eccezioni pre-create
  • Stack trace abilitato vs disabilitato
  • Stack trace richiesto vs mai richiesto
  • Catturato al livello superiore vs rethrown ad ogni livello vs incatenato / avvolto ad ogni livello
  • Vari livelli di Java call stack depth
  • Nessuna ottimizzazione inlining vs extreme inlining vs impostazioni predefinite
  • Campi definiti dall'utente read vs not read

Li confronta anche con le prestazioni di controllo di un codice di errore a vari livelli di frequenza di errore.

Le conclusioni (citate testualmente dal suo post) sono state:

  1. Le eccezioni veramente eccezionali sono magnificamente performanti. Se li usi come progettati e comunichi solo i casi veramente eccezionali tra il numero schiacciante di casi non eccezionali gestito dal codice normale, quindi l'utilizzo delle eccezioni è la vittoria delle prestazioni.

  2. I costi di prestazione delle eccezioni hanno due componenti principali: stack trace construction quando l'eccezione viene istanziata e stack unwinding durante il lancio delle eccezioni.

  3. I costi di costruzione della traccia dello stack sono proporzionali alla profondità dello stack al momento dell'istanza di eccezione. Questo è già male perché chi sulla Terra conosce la profondità della pila a cui questo il metodo di lancio sarebbe chiamato? Anche se si disattiva la generazione della traccia dello stack e / o si memorizzano nella cache le eccezioni, è possibile eliminare solo questa parte del costo delle prestazioni.

  4. I costi di svolgimento dello stack dipendono da quanto siamo fortunati ad avvicinare il gestore delle eccezioni nel codice compilato. Strutturare attentamente il codice per evitare una ricerca approfondita dei gestori di eccezioni probabilmente ci aiuta a essere più fortunati.

  5. Dovremmo eliminare entrambi gli effetti, le prestazioni il costo delle eccezioni è quello della filiale locale. Non importa quanto sia bello, ciò non significa che dovresti usare le eccezioni come il solito flusso di controllo, perché in quel caso sei in balia dell'ottimizzazione del compilatore! Dovresti usarli solo in casi veramente eccezionali, dove la frequenza delle eccezioni ammortizza il possibile costo sfortunato di aumentare l'eccezione effettiva.

  6. La regola ottimistica sembra essere 10^-4 frequenza per le eccezioni sono abbastanza eccezionali. Questo, ovviamente, dipende dai pesi delle eccezioni stesse, dalle azioni esatte intraprese nei gestori di eccezioni, ecc.

Il risultato è che quando un'eccezione non viene generata, non si paga un costo, quindi quando la condizione eccezionale è sufficientemente rara la gestione delle eccezioni è più veloce dell'utilizzo di un if ogni volta. Il post completo è molto merita una lettura.

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

La mia risposta, sfortunatamente, è troppo lunga per postare qui. Quindi permettetemi di riassumere qui e fare riferimento a http://www.fuwjax.com/how-slow-are-java-exceptions / per i dettagli grintosi.

La vera domanda qui non è "Quanto sono lenti i" fallimenti segnalati come eccezioni "rispetto a" codice che non fallisce mai"?"come la risposta accettata potrebbe farti credere. Invece, la domanda dovrebbe essere "Quanto sono lenti i" fallimenti segnalati come eccezioni " rispetto ai fallimenti segnalati in altri modi?" Generalmente, gli altri due modi di segnalare errori sono con valori sentinel o con wrapper di risultati.

I valori Sentinel sono un tentativo di restituire una classe in caso di successo e un'altra in caso di errore. Puoi pensarlo quasi come restituire un'eccezione invece di lanciarne una. Ciò richiede una classe genitore condivisa con l'oggetto success e quindi eseguire un controllo "instanceof" e un paio di cast per ottenere le informazioni di successo o di errore.

Si scopre che a il rischio di sicurezza del tipo, i valori Sentinel sono più veloci delle eccezioni, ma solo di un fattore di circa 2x. Ora, potrebbe sembrare molto, ma quel 2x copre solo il costo della differenza di implementazione. In pratica, il fattore è molto più basso poiché i nostri metodi che potrebbero fallire sono molto più interessanti di alcuni operatori aritmetici come nel codice di esempio altrove in questa pagina.

I wrapper dei risultati, d'altra parte, non sacrificano affatto la sicurezza del tipo. Avvolgono il successo e il fallimento informazioni in una singola classe. Quindi invece di" instanceof "forniscono un" isSuccess () " e getter sia per gli oggetti success che failure. Tuttavia, gli oggetti result sono approssimativamente 2x più lenti rispetto all'utilizzo delle eccezioni. Si scopre che la creazione di un nuovo oggetto wrapper ogni volta è molto più costosa rispetto al lancio di un'eccezione a volte.

Oltre a ciò, le eccezioni sono la lingua fornita per indicare che un metodo potrebbe fallire. Non c'è altro modo per dire da solo il API quali metodi dovrebbero sempre (principalmente) funzionare e che dovrebbero segnalare un errore.

Le eccezioni sono più sicure delle sentinelle, più veloci degli oggetti risultato e meno sorprendenti di entrambe. Non sto suggerendo che try / catch sostituisca if / else, ma le eccezioni sono il modo giusto per segnalare l'errore, anche nella logica di business.

Detto questo, vorrei sottolineare che i due modi più frequenti di influire sostanzialmente sulle prestazioni che ho incontrato stanno creando inutili oggetti e loop annidati. Se è possibile scegliere tra la creazione di un'eccezione o la non creazione di un'eccezione, non creare l'eccezione. Se si può scegliere tra la creazione di un'eccezione a volte o la creazione di un altro oggetto per tutto il tempo, quindi creare l'eccezione.

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

Ho esteso le risposte date da @Mecki e @incarnate, senza riempimento stacktrace per Java.

Con Java 7+, possiamo usare Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). Ma per Java6, vedi la mia risposta a questa domanda

// 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() );
}

Uscita con Java 1.6.0_45, su Core i7, 8 GB di 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

Quindi, ancora i metodi che restituiscono valori sono più veloci, rispetto ai metodi che generano eccezioni. IMHO, non possiamo progettare un'API chiara usando solo i tipi di ritorno per entrambi i flussi di successo e di errore. Metodo che genera eccezioni senza stacktrace sono 4-5 volte più veloci delle normali eccezioni.

Modifica: NoStackTraceThrowable.il nostro sito utilizza cookie tecnici e di terze parti.]}

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

Non so se questi argomenti si riferiscono, ma una volta volevo implementare un trucco basandosi sulla traccia dello stack del thread corrente: volevo scoprire il nome del metodo, che ha attivato l'istanziazione all'interno della classe istanziata (yeap, l'idea è pazza, l'ho completamente abbandonato). Così ho scoperto che chiamare Thread.currentThread().getStackTrace()è estremamente lento (a causa del metodo nativo dumpThreads che utilizza internamente).

Quindi Java Throwable, corrispondentemente, ha un metodo nativo fillInStackTrace. Penso che il killer - catch blocco descritto in precedenza innesca in qualche modo l'esecuzione di questo metodo.

Ma lasciate che vi racconti un'altra storia...

In Scala alcune funzionalità funzionali sono compilate in JVM usando ControlThrowable, che estende Throwable e sovrascrive il suo fillInStackTrace nel modo seguente:

override def fillInStackTrace(): Throwable = this

Così ho adattato il test sopra (la quantità di cicli è diminuita di dieci, la mia macchina è un po ' più lenta :):

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)

}

Quindi, i risultati sono:

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

Vedi, l'unica differenza tra method3 e {[12] } è che lanciano diversi tipi di eccezioni. Sì, method4 è ancora più lento di method1 e method2, ma la differenza è molto più accettabile.

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

Penso che il primo articolo si riferisca all'atto di attraversare lo stack di chiamate e creare una traccia di stack come parte costosa, e mentre il secondo articolo non lo dice, penso che sia la parte più costosa della creazione di oggetti. John Rose ha un articolo in cui descrive diverse tecniche per accelerare le eccezioni. (Preallocazione e riutilizzo di un'eccezione, eccezioni senza tracce di stack, ecc.)

Ma ancora-penso che questo dovrebbe essere considerato solo un necessario il male, l'ultima risorsa. La ragione di John per farlo è emulare le funzionalità in altre lingue che non sono (ancora) disponibili nella JVM. NON dovresti prendere l'abitudine di usare eccezioni per il flusso di controllo. Soprattutto non per motivi di prestazioni! Come tu stesso menziona in #2, rischi di mascherare gravi bug nel tuo codice in questo modo, e sarà più difficile da mantenere per i nuovi programmatori.

I microbenchmark in Java sono sorprendentemente difficili da ottenere (mi è stato detto), specialmente quando si ottiene nel territorio JIT, quindi dubito davvero che l'uso delle eccezioni sia più veloce del "ritorno" nella vita reale. Ad esempio, sospetto che tu abbia da qualche parte tra 2 e 5 stack frame nel tuo test? Ora immagina che il tuo codice verrà invocato da un componente JSF distribuito da JBoss. Ora potresti avere una traccia di stack lunga diverse pagine.

Forse potresti pubblicare il tuo codice di prova?

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

Ho fatto alcuni test delle prestazioni con JVM 1.5 e l'utilizzo delle eccezioni è stato almeno 2 volte più lento. In media: tempo di esecuzione su un metodo banalmente piccolo più che triplicato (3x) con eccezioni. Un ciclo banalmente piccolo che doveva catturare l'eccezione ha visto un aumento di 2 volte nel tempo di auto.

Ho visto numeri simili nel codice di produzione e micro benchmark.

Le eccezioni dovrebbero sicuramente NO essere utilizzato per tutto ciò che viene chiamato frequentemente. Lanciare un migliaia di eccezioni un secondo causerebbe un enorme collo di bottiglia.

Ad esempio, usando "Integer.ParseInt(...) "per trovare tutti i valori errati in un file di testo molto grande idea pessima idea. (Ho visto questo metodo di utilità uccidere prestazioni sul codice di produzione)

Utilizzo di un'eccezione per segnalare un valore errato in un modulo GUI utente, probabilmente non così male dal punto di vista delle prestazioni.

Indipendentemente dal fatto che sia una buona pratica di progettazione, andrei con la regola: se l'errore è normale / previsto, quindi utilizzare un valore di ritorno. Se è anormale, usa un'eccezione. Ad esempio: leggendo gli input dell'utente, i valori errati sono normali use usa un codice di errore. Passando un valore a una funzione di utilità interna, i valori errati dovrebbero essere filtrati chiamando code use use an exception.

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

Qualche tempo fa ho scritto una classe per testare le prestazioni relative della conversione di stringhe in int usando due approcci: (1) chiama Integer.parseInt () e cattura l'eccezione, o (2) abbina la stringa con una regex e chiama parseInt () solo se la corrispondenza ha esito positivo. Ho usato la regex nel modo più efficiente possibile (cioè, creando gli oggetti Pattern e Matcher prima di intering il ciclo), e non ho stampato o salvato gli stacktraces dalle eccezioni.

Per un elenco di diecimila stringhe, se fossero tutti numeri validi, l'approccio parseInt () era quattro volte più veloce dell'approccio regex. Ma se solo l ' 80% delle stringhe fosse valido, la regex era due volte più veloce di parseInt(). E se il 20% fosse valido, il che significa che l'eccezione è stata generata e catturata l ' 80% delle volte, la regex era circa venti volte più veloce di parseInt().

Sono rimasto sorpreso dal risultato, considerando che l'approccio regex elabora due volte le stringhe valide: una volta per la partita e di nuovo per parseInt(). Ma lanciare e la cattura di eccezioni più che compensato per questo. Questo tipo di situazione non è probabile che si verifichi molto spesso nel mondo reale, ma se lo fa, sicuramente non dovresti usare la tecnica di cattura delle eccezioni. Ma se stai solo convalidando l'input dell'utente o qualcosa del genere, usa l'approccio parseInt ().

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

Anche se lanciare un'eccezione non è lento, è comunque una cattiva idea lanciare eccezioni per il normale flusso del programma. Usato in questo modo è analogo a un GOTO...

Immagino che non risponda davvero alla domanda. Immagino che la saggezza "convenzionale" di lanciare eccezioni lente fosse vera nelle versioni precedenti di java (

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

HotSpot è perfettamente in grado di rimuovere il codice di eccezione per le eccezioni generate dal sistema, purché sia tutto in linea. Tuttavia, l'eccezione creata esplicitamente e quelle altrimenti non rimosse trascorrono molto tempo a creare la traccia dello stack. Sovrascrivi fillInStackTrace per vedere come questo può influire sulle prestazioni.

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

Le prestazioni delle eccezioni in Java e C# lasciano molto a desiderare.

Come programmatori questo ci costringe a vivere secondo la regola "le eccezioni dovrebbero essere causate raramente", semplicemente per ragioni pratiche di prestazioni.

Tuttavia, come scienziati informatici, dovremmo ribellarci a questo stato problematico. La persona che crea una funzione spesso non ha idea di quanto spesso verrà chiamata, o se il successo o il fallimento è più probabile. Solo il chiamante ha queste informazioni. Cercando di evitare le eccezioni portano a idom API poco chiare in cui in alcuni casi abbiamo solo versioni di eccezione pulite ma lente, e in altri casi abbiamo errori di valore di ritorno veloci ma goffi, e in altri casi ci ritroviamo con entrambi. L'implementatore della libreria potrebbe dover scrivere e mantenere due versioni di API e il chiamante deve decidere quale delle due versioni utilizzare in ogni situazione.

Questo è un po ' un casino. Se le eccezioni avessero prestazioni migliori, potremmo evitare questi idiomi goffi e usare le eccezioni come dovevano essere usati... come struttura di ritorno degli errori strutturata.

Mi piacerebbe davvero vedere i meccanismi di eccezione implementati usando tecniche più vicine ai valori di ritorno, quindi potremmo avere prestazioni più vicine ai valori di ritorno.. poiché questo è ciò a cui ritorniamo nel codice sensibile alle prestazioni.

Ecco un esempio di codice che confronta le prestazioni delle eccezioni con le prestazioni del valore di ritorno dell'errore.

TestIt di classe pubblica {

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);              
        }
    }
}

}

E qui ci sono i risultati:

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

Il controllo e la propagazione dei valori di ritorno aggiungono un costo rispetto alla chiamata baseline-null e tale costo è proporzionale alla profondità di chiamata. A una profondità della catena di chiamata di 8, la versione di controllo del valore di ritorno dell'errore era circa il 27% più lenta della versione della basline che non controllava i valori di ritorno.

Le prestazioni delle eccezioni, in confronto, non sono una funzione della profondità di chiamata, ma della frequenza delle eccezioni. Tuttavia, la degradazione all'aumentare della frequenza delle eccezioni è molto più drammatica. Con una frequenza di errore del 25%, il codice è stato eseguito 24 VOLTE più lentamente. Con una frequenza di errore del 100%, la versione di eccezione è quasi 100 VOLTE più lenta.

Questo mi suggerisce che forse stanno facendo i compromessi sbagliati nelle nostre implementazioni di eccezione. Le eccezioni potrebbero essere più veloci, evitando costose stalk-walks o trasformandole completamente in controllo del valore di ritorno supportato dal compilatore. Finché non lo fanno, siamo bloccati a evitarli quando vogliamo che il nostro codice funzioni velocemente.

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

Basta confrontare diciamo Integer.parseInt al seguente metodo, che restituisce solo un valore predefinito nel caso di dati non eseguibili invece di lanciare un'eccezione:

  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;
  }

Finché si applicano entrambi i metodi a dati "validi", entrambi funzioneranno approssimativamente alla stessa velocità (anche se Integer.parseInt riesce a gestire dati più complessi). Ma non appena si tenta di analizzare dati non validi (ad esempio per analizzare "abc" 1.000.000 volte), la differenza di prestazioni dovrebbe essere essenziale.

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

Ho cambiato la risposta di @Mecki sopra per far sì che method1 restituisca un booleano e un check nel metodo chiamante, poiché non puoi semplicemente sostituire un'eccezione con nulla. Dopo due esecuzioni, method1 era ancora il più veloce o veloce come method2.

Ecco un'istantanea del codice:

// 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!");
            }
    }

E risultati:

Eseguire 1

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

Eseguire 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

Ottimo post sulle prestazioni delle eccezioni è:

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

Istanziazione vs riutilizzo esistente, con stack trace e senza, ecc.:

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

A seconda della profondità della traccia dello stack:

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

Per altri dettagli (incluso l'assemblatore x64 di JIT) leggi il post del blog originale.

Ciò significa che Hibernate / Spring / etc-EE-shit è lento a causa delle eccezioni (xD) e la riscrittura del flusso di controllo delle app lontano dalle eccezioni (sostituiscilo con continure / break e restituendo boolean flag come in C dalla chiamata al metodo) migliora le prestazioni della tua applicazione 10x-100x, a seconda di quanto spesso li lanci))

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

La mia opinione sulla velocità delle eccezioni rispetto al controllo dei dati a livello di codice.

Molte classi avevano convertitore da stringa a valore (scanner / parser), anche librerie rispettate e ben note;)

Di solito ha forma

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

Il nome dell'eccezione è solo un esempio, di solito è deselezionato (runtime), quindi la dichiarazione di lancio è solo la mia immagine

A volte esistono seconda forma:

public static Example Parse(String input, Example defaultValue)

Non lanciare mai

Quando il secondo non è disponibile (o il programmatore legge troppo meno documenti e usa solo prima), scrivi tale codice con espressione regolare. Le espressioni regolari sono cool, politicamente corrette ecc:

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

Con questo codice i programmatori non hanno costi di eccezioni. MA HA un costo molto ELEVATO comparabile delle espressioni regolari SEMPRE rispetto al piccolo costo dell'eccezione a volte.

Io uso quasi sempre in tale contesto

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

Senza analizzare stacktrace ecc., credo che dopo le tue lezioni sia abbastanza veloce.

Non abbiate paura Eccezioni

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

Perché le eccezioni dovrebbero essere più lente dei normali ritorni?

Finché non si stampa lo stacktrace sul terminale, lo si salva in un file o qualcosa di simile, il blocco catch non funziona più di altri blocchi di codice. Quindi, non riesco a immaginare perché "throw new my_cool_error()" dovrebbe essere così lento.

Buona domanda e non vedo l'ora di ulteriori informazioni su questo argomento!

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