Quali sono gli effetti delle eccezioni sulle prestazioni in Java?


496

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

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

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

e

  1. è disordinato perché le persone si aspettano che vengano gestiti solo errori con un 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 nella creazione dell'oggetto eccezione che viene generato. Non è veloce." L'articolo Gestione efficace delle eccezioni in Java afferma che "la ragione di ciò è dovuta all'aspetto della creazione di oggetti della gestione delle eccezioni, che rende intrinsecamente lente le eccezioni". Un altro motivo è che la generazione dello stack stack è ciò che la rallenta.

I miei test (utilizzando Java 1.6.0_07, Java HotSpot 10.0, su Linux a 32 bit), indicano che la gestione delle eccezioni non è più lenta del codice normale. Ho provato a eseguire un metodo in un ciclo che esegue del codice. Alla fine del metodo, uso un booleano per indicare se tornare o lanciare . In questo modo l'elaborazione effettiva è la stessa. Ho provato a eseguire i metodi in diversi ordini e in media i miei tempi di test, pensando che potrebbe essere stato il riscaldamento della JVM. In tutti i miei test, il lancio è stato almeno altrettanto veloce del 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 in termini di esempio di codice, confronti di test o risultati nell'ultimo anno o due che mostrano che la gestione delle eccezioni in Java è effettivamente lento.

Ciò che mi ha portato in questo percorso è stata un'API che avevo bisogno di usare che ha generato 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 il loro modo di pensare in avanti?

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


8
So che non ti stavi chiedendo 2), ma dovresti davvero riconoscere che usare un'eccezione per il flusso del programma non è meglio dell'uso di GOTO. Alcune persone difendono i goto, alcune persone difenderebbero ciò di cui stai parlando, ma se chiedi a qualcuno che ha implementato e mantenuto o per un periodo di tempo, ti diranno che entrambi sono poveri e difficili da mantenere pratiche di progettazione (e probabilmente malediranno il nome della persona che pensava di essere abbastanza intelligente da prendere la decisione di usarli).
Bill K,

80
Bill, affermando che l'uso delle eccezioni per il flusso del programma non è meglio dell'uso dei GOTO non è meglio che affermare che l'uso di condizionali e loop per il flusso del programma non è migliore dell'uso dei GOTO. È un'aringa rossa. Spiegati. Le eccezioni possono e sono utilizzate in modo efficace per il flusso del programma in altre lingue. Il codice Python idiomatico utilizza regolarmente le eccezioni, ad esempio. Posso e ho mantenuto il codice che utilizza le eccezioni in questo modo (non Java però) e non penso che ci sia qualcosa di intrinsecamente sbagliato in esso.
mmalone

14
@mmalone utilizzando Exceptions per il normale flusso di controllo è una cattiva idea in Java perché la scelta del paradigma è stata fatta in quel modo . Leggi Bloch EJ2 - afferma chiaramente che, citazione, (Articolo 57) exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow- che fornisce una spiegazione completa e completa del perché. Ed era il ragazzo che ha scritto Java lib. Pertanto, è lui a definire il contratto API delle classi. / d'accordo Bill K su questo.

8
@ OndraŽižka Se un framework fa questo (usa Eccezioni in condizioni non eccezionali), è difettoso e rotto dal design, rompendo il contratto di classe Exception del linguaggio. Solo perché alcune persone scrivono codice pessimo non lo rende meno pessimo.

8
Nient'altro che il creatore di stackoverflow.com ha torto sulle eccezioni. La regola d'oro dello sviluppo del software non è mai rendere il semplice complesso e ingombrante. Scrive: "È vero che quello che dovrebbe essere un semplice programma a 3 righe sboccia spesso a 48 righe quando si mette un buon controllo degli errori, ma questa è la vita, ..." Questa è una ricerca di purezza, non di semplicità.
sf_jeff

Risposte:


345

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 (il che richiede già del tempo) e forse alcuni altri dati devono essere creati ... tutto ciò accade già nell'istruzione try. L'istruzione throw deve sciogliere lo stack e ripristinare i valori di tutti i registri (e possibili altri valori nella VM). Quindi provare e lanciare sono ugualmente lenti, e questo è piuttosto lento, tuttavia se non viene generata alcuna eccezione, uscire dal blocco try non richiede tempo nella maggior parte dei casi (poiché tutto viene messo nello stack che pulisce automaticamente se il metodo esiste).

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

... ma stai scrivendo in Java, quindi il tuo codice in seguito verrà eseguito solo su una JVM su un sistema specifico? Dal momento che se può mai funzionare su qualsiasi altra piattaforma o qualsiasi altra versione JVM (possibilmente di qualsiasi altro fornitore), chi dice che usano anche l'implementazione veloce? Quello veloce è più complicato di quello lento e non è facilmente possibile su tutti i sistemi. Vuoi rimanere portatile? Quindi non fare affidamento sul fatto che le eccezioni siano veloci.

Inoltre fa una grande differenza ciò 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é JIT può effettivamente trattare un lancio come un semplice goto. Non ha bisogno di salvare lo stato dello stack né di sciogliere lo stack se viene generata un'eccezione (deve solo passare ai gestori di cattura). Tuttavia, questo non è quello che fai di solito. Di solito apri un blocco try e poi chiami un metodo che potrebbe generare un'eccezione, giusto? E anche se usi semplicemente il blocco try nel tuo metodo, che tipo di metodo sarà, che non chiama nessun altro metodo? Calcolerà solo un numero? Allora per cosa hai bisogno di eccezioni? Esistono modi molto più eleganti per regolare il flusso del programma. Per praticamente qualsiasi altra cosa tranne la semplice matematica,

Vedi il 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 del 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 già detto, il risultato non sarà così negativo se si inserisce try / catch e si lancia tutto nello stesso metodo (metodo3), ma questa è un'ottimizzazione JIT speciale su cui non farei affidamento. E anche quando si utilizza questa ottimizzazione, il lancio è ancora piuttosto lento. Quindi non so cosa stai cercando di fare qui, ma c'è sicuramente un modo migliore per farlo che usare try / catch / throw.


7
Ottima risposta, ma vorrei solo aggiungere che, per quanto ne so, System.nanoTime () dovrebbe essere utilizzato per misurare le prestazioni, non System.currentTimeMillis ().
Simon Forsberg,

10
@ SimonAndréForsberg nanoTime()richiede Java 1.5 e avevo solo Java 1.4 disponibile sul sistema che ho usato per scrivere il codice sopra. Inoltre non gioca un ruolo enorme nella pratica. L'unica differenza tra i due è che uno è nanosecondo l'altro millisecondi e che nanoTimenon è influenzato dalle manipolazioni dell'orologio (che sono irrilevanti, a meno che tu o il processo di sistema modifichi l'orologio di sistema esattamente nel momento in cui il codice di test è in esecuzione). Generalmente hai ragione, tuttavia, nanoTimeè ovviamente la scelta migliore.
Mecki,

2
Va notato che il test è un caso estremo. Mostra un hit di prestazione molto piccolo per il codice con un tryblocco, ma no throw. Il throwtest genera eccezioni il 50% delle volte che passa attraverso try. È chiaramente una situazione in cui il fallimento non è eccezionale . Riducendolo solo al 10%, si riduce drasticamente il successo delle prestazioni. Il problema con questo tipo di test è che incoraggia le persone a smettere del tutto di usare le eccezioni. L'uso delle eccezioni, per un'eccezionale gestione dei casi, offre prestazioni decisamente migliori rispetto a quanto mostrato dal test.
Nate

1
@Nate Prima di tutto, ho detto molto chiaramente che tutto ciò dipende da come vengono implementate le eccezioni. Stavo solo testando un'implementazione specifica, ma ce ne sono molte e Oracle potrebbe sceglierne una completamente diversa ad ogni versione. In secondo luogo, se le eccezioni sono solo eccezionali, ciò che di solito sono, ovviamente l'impatto è minore, questo è così ovvio, che davvero non credo che uno debba indicarlo esplicitamente e quindi non riesco affatto a capire il tuo punto qui. E in terzo luogo, l'eccezione ne fa un uso eccessivo, tutti sono d'accordo, quindi usarli con molta cura è una cosa molto buona.
Mecki,

4
@Glide Un tiro non è come un pulito return. Lascia un metodo da qualche parte nel mezzo del corpo, forse anche nel mezzo di un'operazione (che finora è stata completata solo del 50%) e il catchblocco può essere 20 stack frame verso l'alto (un metodo ha un tryblocco, chiamando method1, che chiama method2, che chiama mehtod3, ... e in method20 nel mezzo di un'operazione viene generata un'eccezione). Lo stack deve essere svolto a 20 frame verso l'alto, tutte le operazioni non completate devono essere annullate (le operazioni non devono essere eseguite a metà) e i registri della CPU devono essere puliti. Tutto ciò richiede tempo.
Mecki,

256

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 laptop è ovviamente più lento).

method4 è identico a method3 tranne per il fatto che crea un new Integer(1)piuttosto che fare throw new Exception().

method5 è come method3 tranne per il fatto che crea il new Exception()senza lanciarlo.

method6 è come method3 tranne per il fatto che genera un'eccezione pre-creata (una variabile di istanza) anziché crearne una nuova.

In Java gran parte delle spese per il lancio di un'eccezione è il tempo impiegato per raccogliere la traccia dello stack, che si verifica quando viene creato l'oggetto eccezione. Il costo effettivo del lancio dell'eccezione, sebbene elevato, è notevolmente inferiore al costo di creazione dell'eccezione.


49
+1 La tua risposta risolve il problema principale: il tempo impiegato per svolgere e tracciare lo stack e, in secondo luogo, il lancio dell'errore. Avrei selezionato questa come risposta finale.
Ingegnere

9
simpatico. ~ 70% creando l'eccezione, ~ 30% lanciandola. buone informazioni.
Chaqke,

1
@Basil - Dovresti essere in grado di capirlo dai numeri sopra.
Hot Licks,

2
@HotLicks e questo è esattamente il motivo per cui è importante dire quale versione di Java è stata usata nel post
Thorbjørn Ravn Andersen,

3
Possiamo notare che nel codice standard, la creazione e il lancio di eccezioni si verifica in rari casi (in fase di esecuzione intendo), se non è il caso, o le condizioni di runtime sono pessime o la progettazione stessa è il problema; in entrambi i casi le esibizioni non sono un problema ...
Jean-Baptiste Yunès

70

Aleksey Shipilëv ha effettuato un'analisi molto approfondita in cui confronta le eccezioni Java in varie combinazioni di condizioni:

  • Eccezioni appena create vs eccezioni pre-create
  • Stack stack abilitato vs disabilitato
  • Traccia stack richiesta vs mai richiesta
  • Catturato ai massimi livelli contro la riconfigurazione ad ogni livello contro incatenato / avvolto ad ogni livello
  • Vari livelli di profondità dello stack di chiamate Java
  • Nessuna ottimizzazione incorporata o impostazioni estreme rispetto a quelle predefinite
  • Campi definiti dall'utente letti vs non letti

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

Le conclusioni (citate alla lettera dal suo post) sono state:

  1. Eccezioni davvero eccezionali sono meravigliosamente performanti. Se li usi come progettato e comunichi solo i casi veramente eccezionali tra il numero enormemente eccezionale di casi non eccezionali gestiti da un codice normale, l'utilizzo delle eccezioni è la vittoria delle prestazioni.

  2. I costi di prestazione delle eccezioni hanno due componenti principali: costruzione della traccia dello stack quando viene istanziata l'eccezione e svolgimento dello stack durante il lancio dell'eccezione .

  3. I costi di costruzione della traccia dello stack sono proporzionali alla profondità dello stack al momento dell'istanza dell'eccezione. Questo è già negativo perché chi sulla Terra conosce la profondità dello stack a cui questo 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 con cura il codice per evitare la ricerca approfondita dei gestori delle eccezioni probabilmente ci sta aiutando a diventare più fortunati.

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

  6. La regola empirica ottimistica sembra essere una frequenza di 10 ^ -4 per le eccezioni è abbastanza eccezionale. Ciò, ovviamente, dipende dai pesi massimi delle eccezioni stesse, dalle azioni esatte intraprese nei gestori delle eccezioni, ecc.

Il risultato è che quando non viene generata un'eccezione, non si paga un costo, quindi quando la condizione eccezionale è sufficientemente rara, la gestione delle eccezioni è più veloce dell'uso ifogni volta. Vale la pena leggere l'intero post.


41

La mia risposta, sfortunatamente, è troppo lunga per essere pubblicata qui. Vorrei quindi 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" guasti segnalati come eccezioni "rispetto al" codice che non fallisce mai "?" poiché la risposta accettata potrebbe farti credere. Invece, la domanda dovrebbe essere "Quanto sono lenti i" guasti segnalati come eccezioni "rispetto ai guasti segnalati in altri modi?" In genere, gli altri due modi per segnalare errori sono con valori sentinella o con wrapper di risultati.

I valori di Sentinel sono un tentativo di restituire una classe in caso di successo e un'altra in caso di fallimento. 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 esegue il cast per ottenere le informazioni sull'esito positivo o negativo.

Si scopre che a rischio di sicurezza del tipo, i valori di Sentinel sono più veloci delle eccezioni, ma solo di un fattore circa 2 volte. Ora, può 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 di risultato, d'altra parte, non sacrificano affatto la sicurezza del tipo. Avvolgono le informazioni di successo e fallimento in una singola classe. Quindi invece di "instanceof" forniscono un "isSuccess ()" e getter sia per gli oggetti successo che per quelli non riusciti. Tuttavia, gli oggetti risultato sono circa 2 volte 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.

Inoltre, le eccezioni sono il linguaggio fornito per indicare che un metodo potrebbe fallire. Non c'è altro modo di dire solo dall'API quali metodi dovrebbero funzionare (principalmente) sempre e quali dovrebbero riportare errori.

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 un errore, anche nella logica aziendale.

Detto questo, vorrei sottolineare che i due modi più frequenti per influire sostanzialmente sulle prestazioni che ho incontrato sono la creazione di oggetti non necessari e cicli nidificati. Se è possibile scegliere tra la creazione di un'eccezione o la non creazione di un'eccezione, non creare l'eccezione. Se è possibile scegliere tra la creazione di un'eccezione a volte o la creazione di un altro oggetto in qualsiasi momento, creare l'eccezione.


5
Ho deciso di testare le prestazioni a lungo termine delle tre implementazioni rispetto a un'implementazione di controllo che verifica la presenza di errori senza riportare. Il processo ha un tasso di fallimento di circa il 4%. Un'iterazione di un test richiama il processo 10000 volte contro una delle strategie. Ogni strategia viene testata 1000 volte e le ultime 900 volte vengono utilizzate per generare le statistiche. Ecco i tempi medi in nanos: Controllo 338 Eccezione 429 Risultato 348 Sentinel 345
Fuwjax

2
Solo per divertimento ho disabilitato fillInStackTrace nel test delle eccezioni. Ecco i tempi ora: Controllo 347 Eccezione 351 Risultato 364 Sentinella 355
Fuwjax

1
Fuwjax, a meno che non mi manchi qualcosa (e ammetto di aver letto solo il tuo post SO, non il tuo post sul blog), sembra che i tuoi due commenti sopra siano in contraddizione con il tuo post. Presumo che numeri più bassi siano migliori nel tuo benchmark, giusto? In tal caso, la generazione di eccezioni con fillInStackTrace abilitato (che è il comportamento predefinito e normale), comporta prestazioni più lente rispetto alle altre due tecniche descritte. Mi sto perdendo qualcosa o hai effettivamente commentato per confutare il tuo post?
Felix GV,

@Fuwjax - il modo per evitare la scelta "rock and hard place" che presenti qui è quello di pre-allocare un oggetto che rappresenta "successo". Di solito si possono anche pre-allocare oggetti per i casi di errore comuni. Quindi, solo nel raro caso di riporto di ulteriori dettagli, viene creato un nuovo oggetto. (Questo è l'equivalente OO dei "codici di errore" interi, oltre a una chiamata separata per ottenere i dettagli dell'ultimo errore - una tecnica che esiste da decenni.)
ToolmakerSteve

@Fuwjax Quindi lanciare un'eccezione non crea un oggetto dal tuo account? Non sono sicuro di aver capito quel ragionamento. Sia che tu lanci un'eccezione o restituisca un oggetto risultato, stai creando oggetti. In tal senso, gli oggetti non sono più lenti del lancio di un'eccezione.
Matthias,

20

Ho esteso le risposte fornite da @Mecki e @incarnate , senza riempire 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, i metodi che restituiscono valori sono ancora più veloci rispetto ai metodi che generano eccezioni. IMHO, non possiamo progettare un'API chiara usando solo i tipi restituiti per i flussi di successo e di errore. I metodi che generano eccezioni senza stacktrace sono 4-5 volte più veloci delle normali eccezioni.

Modifica: NoStackTraceThrowable.java Grazie @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

interessante, grazie. Ecco la dichiarazione di classe mancante:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
Greg,

all'inizio hai scritto With Java 7+, we can usema dopo hai scritto Output with Java 1.6.0_45,quindi questo è il risultato Java 6 o 7?
WBAR

1
@WBAR da Java 7, dobbiamo solo usare il Throwablecostruttore che ha boolean writableStackTracearg. Ma questo non è presente in Java 6 e versioni precedenti. Ecco perché ho fornito un'implementazione personalizzata per Java 6 e versioni precedenti. Quindi il codice sopra è per Java 6 e sotto. Leggere attentamente la prima riga del secondo paragrafo.
manikanta,

@manikanta "IMHO, non possiamo progettare un'API chiara usando solo i tipi di ritorno sia per i flussi di successo che per quelli di errore".
Hejazzman,

@Hejazzman sono d'accordo. Ma Optionalo simili sono arrivati ​​un po 'tardi a Java. Prima di ciò abbiamo anche usato oggetti wrapper con flag di successo / errore. Ma sembra essere un po 'hack e non mi sembra naturale.
Manikanta,

8

Qualche tempo fa ho scritto una classe per testare le prestazioni relative della conversione di stringhe in in utilizzando due approcci: (1) chiama Integer.parseInt () e cattura l'eccezione, oppure (2) abbina la stringa con un regex e chiama parseInt () solo se la partita ha successo. Ho usato regex nel modo più efficiente possibile (ovvero creando gli oggetti Pattern e Matcher prima di interporre il ciclo) e non ho stampato o salvato le stack stack 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 lanciata e catturata l'80% delle volte, la regex è stata circa venti volte più veloce di parseInt ().

Sono stato sorpreso dal risultato, considerando che l'approccio regex elabora due volte stringhe valide: una volta per la corrispondenza e ancora per parseInt (). Ma lanciare e catturare eccezioni più che compensato. È probabile che questo tipo di situazione non si verifichi molto spesso nel mondo reale, ma in tal caso, non dovresti assolutamente usare la tecnica di cattura delle eccezioni. Ma se stai solo convalidando l'input dell'utente o qualcosa del genere, usa sicuramente l'approccio parseInt ().


quale JVM hai usato? è ancora così lento con sun-jdk 6?
Benedikt Waldvogel,

L'ho scavato e l'ho eseguito di nuovo sotto JDK 1.6u10 prima di inviare quella risposta, e questi sono i risultati che ho pubblicato.
Alan Moore,

Questo è molto, molto utile! Grazie. Per i miei soliti casi d'uso ho bisogno di analizzare gli input dell'utente (usando qualcosa di simile Integer.ParseInt()) e mi aspetto che la maggior parte delle volte l'input dell'utente sia corretto, quindi per il mio caso d'uso sembra che prendere l'eccezionale hit hit sia la strada da percorrere .
markvgti,

8

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

I microbenchmark in Java sono sorprendentemente difficili da ottenere (mi è stato detto), specialmente quando entri 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 frame di stack nel tuo test? Ora immagina che il tuo codice verrà invocato da un componente JSF distribuito da JBoss. Ora potresti avere una traccia dello stack che è lunga diverse pagine.

Forse potresti pubblicare il tuo codice di prova?


7

Non so se questi argomenti riguardano, ma una volta volevo implementare un trucco basandomi sulla traccia dello stack del thread corrente: volevo scoprire il nome del metodo, che ha innescato l'istanza all'interno della classe istanziata (sì, l'idea è pazza, L'ho completamente abbandonato). Così ho scoperto che la chiamata Thread.currentThread().getStackTrace()è estremamente lenta (a causa del dumpThreadsmetodo nativo che utilizza internamente).

Quindi Java Throwable, di conseguenza, ha un metodo nativo fillInStackTrace. Penso che il catchblocco killer descritto in precedenza in qualche modo inneschi l'esecuzione di questo metodo.

Ma lascia che ti racconti un'altra storia ...

In Scala alcune funzionalità funzionali sono compilate in JVM usando ControlThrowable, che estende Throwablee sovrascrive le sue fillInStackTracein un modo seguente:

override def fillInStackTrace(): Throwable = this

Quindi ho adattato il test sopra (la quantità di cicli è ridotta 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

Vedete, l'unica differenza tra method3e method4è che generano diversi tipi di eccezioni. Sì, method4è ancora più lento di method1e method2, ma la differenza è molto più accettabile.


6

Ho eseguito 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 più piccolo che triplicato (3x) con eccezioni. Un loop banalmente piccolo che ha dovuto catturare l'eccezione ha visto un aumento di 2 volte del tempo libero.

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

Le eccezioni NON devono assolutamente essere utilizzate per tutto ciò che viene chiamato frequentemente. Lanciare migliaia di eccezioni al 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 - pessima idea. (Ho visto questo metodo di utilità uccidere le prestazioni sul codice di produzione)

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

Che si tratti o meno di una buona pratica di progettazione, seguirei la regola: se l'errore è normale / previsto, utilizzare un valore restituito. Se è anormale, usa un'eccezione. Ad esempio: lettura degli input dell'utente, valori errati sono normali: utilizzare un codice di errore. Passando un valore a una funzione di utilità interna, i valori errati devono essere filtrati chiamando il codice - usare un'eccezione.


Permettimi di suggerire alcune cose che SONO buone da fare: se hai bisogno di un numero in un modulo, invece di utilizzare Integer.valueOf (String), dovresti invece considerare l'utilizzo di un matcher di espressioni regolari. Puoi precompilare e riutilizzare il modello in modo da rendere i matcher economici. Tuttavia, su un modulo GUI, avere isValid / validate / checkField o quello che hai è probabilmente più chiaro. Inoltre, con Java 8 abbiamo monadi opzionali, quindi considera di usarle. (la risposta ha 9 anni, ma comunque!: p)
Haakon Løtveit

4

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

Come programmatori questo ci obbliga a rispettare la regola "le eccezioni dovrebbero essere causate raramente", semplicemente per motivi pratici di prestazione.

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 è più probabile il successo o il fallimento. Solo il chiamante ha queste informazioni. Cercare di evitare eccezioni porta a idomi API poco chiari in cui in alcuni casi abbiamo solo versioni di eccezioni pulite ma lente, e in altri casi abbiamo errori di valore di ritorno rapidi ma goffi, e in altri casi finiamo con entrambi . L'implementatore della libreria potrebbe dover scrivere e gestire due versioni di API e il chiamante deve decidere quale delle due versioni utilizzare in ciascuna situazione.

Questo è un po 'un casino. Se le eccezioni avessero prestazioni migliori, potremmo evitare questi goffi modi di dire e usare le eccezioni perché dovevano essere usate ... come una struttura strutturata per la restituzione dell'errore.

Mi piacerebbe molto 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 torniamo 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 aggiunge un certo costo rispetto alla chiamata nulla di base e tale costo è proporzionale alla profondità della chiamata. A una profondità della catena di chiamate di 8, la versione di verifica del valore di ritorno dell'errore era più lenta di circa il 27% rispetto alla versione di base che non controllava i valori di ritorno.

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

Ciò mi suggerisce che forse stanno facendo degli svantaggi nelle nostre implementazioni delle eccezioni. Le eccezioni potrebbero essere più veloci, evitando costose passeggiate a pedale o trasformandole completamente in controllo del valore restituito supportato dal compilatore. Fino a quando non lo fanno, siamo bloccati ad evitarli quando vogliamo che il nostro codice venga eseguito rapidamente.


3

HotSpot è abbastanza in grado di rimuovere il codice di eccezione per le eccezioni generate dal sistema, purché sia ​​tutto in linea. Tuttavia, l'eccezione creata in modo esplicito e quelle non rimosse altrimenti impiegano molto tempo a creare la traccia dello stack. Sostituisci fillInStackTraceper vedere come questo può influire sulle prestazioni.


2

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

Immagino che non risponda davvero alla domanda però. Immagino che la saggezza 'convenzionale' di lanciare eccezioni sia lenta era vera nelle precedenti versioni di Java (<1.4). La creazione di un'eccezione richiede che la VM crei l'intera traccia dello stack. Da allora sono cambiate molte cose nella VM per accelerare le cose e questa è probabilmente un'area che è stata migliorata.


1
Sarebbe bene definire "normale flusso di programma". Molto è stato scritto sull'uso delle eccezioni verificate come fallimento di un processo aziendale e un'eccezione non selezionata per guasti non recuperabili, quindi, in un certo senso, un fallimento nella logica aziendale potrebbe ancora essere considerato un flusso normale.
Spencer Kormos,

2
@Spencer K: un'eccezione, come suggerisce il nome, significa che è stata scoperta una situazione eccezionale (un file è andato via, una rete improvvisamente chiusa, ...). Ciò implica che la situazione era INASPETTATA. Se si prevede che si verificherà la situazione, non utilizzerei un'eccezione per essa.
Mecki,

2
@Mecki: giusto. Recentemente ho avuto una discussione con qualcuno su questo ... Stavano scrivendo un framework di validazione e stavano lanciando un'eccezione in caso di fallimento della validazione. Penso che questa sia una cattiva idea in quanto sarebbe abbastanza comune. Preferirei vedere il metodo restituire un ValidationResult.
user38051,

2
In termini di flusso di controllo, un'eccezione è analoga a a breako returnnon a goto.
Hot Licks,

3
Ci sono tonnellate di paradigmi di programmazione. Non può esserci un singolo "flusso normale", qualunque cosa tu intenda con questo. Fondamentalmente, il meccanismo di eccezione è solo un modo per lasciare rapidamente il frame corrente e svolgere lo stack fino a un certo punto. La parola "eccezione" non implica nulla della sua natura "inaspettata". Un rapido esempio: è molto naturale "lanciare" 404 dalle applicazioni Web quando si verificano determinate circostanze lungo il percorso. Perché questa logica non dovrebbe essere implementata con eccezioni? Qual è l'anti-pattern?
incarnato l'

2

Basta confrontare diciamo Integer.parseInt al seguente metodo, che restituisce un valore predefinito in caso di dati non analizzabili invece di generare 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;
  }

Se 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 di volte), la differenza nelle prestazioni dovrebbe essere essenziale.


2

Un ottimo post sulle prestazioni delle eccezioni è:

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

Istantanea vs riutilizzo esistente, con stack stack 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 assemblatore x64 di JIT) leggi il post originale del blog.

Ciò significa che Hibernate / Spring / etc-EE-shit sono lenti a causa delle eccezioni (xD) e della riscrittura del flusso di controllo delle app lontano dalle eccezioni (sostituiscilo con continure/ breake restituendo booleanflag come in C dalla chiamata del metodo) migliora le prestazioni della tua applicazione 10x-100x , a seconda di quanto spesso li lancia))


0

Ho cambiato la risposta di @Mecki sopra per fare in modo che method1 restituisca un valore booleano e un controllo 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:

Esegui 1

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

Esegui 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2

0

Un'eccezione è pensato per la gestione di condizioni impreviste in fase di esecuzione solo.

L'uso di un'eccezione al posto della semplice convalida che può essere eseguita in fase di compilazione ritarderà la convalida fino al tempo di esecuzione. Ciò a sua volta ridurrà l'efficienza del programma.

Generare un'eccezione invece di utilizzare una semplice convalida if..else renderà anche il codice complesso da scrivere e da mantenere.


-3

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

Molte classi avevano un convertitore String to value (scanner / parser), librerie rispettate e famose;)

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 esiste una seconda forma:

public static Example Parse(String input, Example defaultValue)

mai lanciare

Quando i secondi non sono disponibili (o il programmatore legge troppo meno documenti e usa solo i primi), scrivi questo codice con un'espressione regolare. L'espressione regolare è interessante, politicamente corretta 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 contro il piccolo costo dell'eccezione a volte.

Uso quasi sempre in tale contesto

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

senza analizzare stacktrace ecc., credo che dopo le lezioni della tua abbastanza velocità.

Non aver paura delle eccezioni


-5

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

Finché non stampi lo stacktrace sul terminale, lo salvi in ​​un file o qualcosa di simile, il blocco catch non fa più lavoro di altri blocchi di codice. Quindi, non riesco a immaginare perché "lanciare nuovo my_cool_error ()" dovrebbe essere così lento.

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


17
L'eccezione deve acquisire le informazioni sulla traccia dello stack, anche se in realtà non viene utilizzata.
Jon Skeet,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.