È finalmente costoso


14

Nel caso di codice in cui è necessario eseguire una pulizia delle risorse prima di uscire da una funzione, esiste una differenza sostanziale nelle prestazioni tra questi 2 modi di farlo.

  1. Pulizia della risorsa prima di ogni dichiarazione di ritorno

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Pulizia della risorsa in un blocco finally

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

Ho fatto alcuni test di base nei programmi di esempio e non sembra esserci molta differenza. Preferisco di gran lunga il finallymodo di farlo, ma mi chiedevo se ciò avrebbe causato un calo delle prestazioni in un grande progetto.


3
Non vale davvero la pena pubblicare come risposta, ma no. Se stai usando Java, che si aspetta che le eccezioni siano la strategia di gestione degli errori predefinita, allora dovresti usare try / catch / infine - la lingua è ottimizzata per il modello che stai usando.
Jonathan Rich,

1
@JonathanRich: come?
haylem,

2
Scrivi il codice in modo pulito in modo tale da esprimere ciò che stai cercando di realizzare. Ottimizza in seguito quando sai di avere un problema - non evitare le funzionalità del linguaggio perché pensi che potresti radere qualche millisecondo di qualcosa che non è lento in primo luogo.
Sean McSomething il

@mikeTheLiar - Se vuoi dire che dovrei scrivere if(!cond), allora è Java che mi ha fatto fare questo. In C ++, è così che scrivo codice sia di booleani che anche di altri tipi, ad es int x; if(!x). Poiché java mi permette di usare questo solo per booleans, ho smesso totalmente di usare if(cond)& if(!cond)in java.
user93353

1
@ user93353 che mi rende triste. Preferirei vedere piuttosto (someIntValue != 0)che confrontare piuttosto che valutare i booleani. Questo mi profuma e lo refactoring immediatamente quando lo vedo in natura.
MikeTheLiar,

Risposte:


23

Come indicato in Quanto sono lente le eccezioni Java? si può vedere che la lentezza try {} catch {}è all'interno dell'istanza dell'eccezione stessa.

La creazione di un'eccezione recupererà l'intero stack di chiamate dal runtime ed è qui che si trova la spesa. Se non si sta creando un'eccezione, si tratta solo di un lieve aumento del tempo.

Nell'esempio fornito in questa domanda non ci sono eccezioni, non ci si aspetterebbe alcun rallentamento nel crearle - non sono create. Invece, ciò che è qui è try {} finally {}gestire la deallocazione delle risorse all'interno del blocco finally.

Quindi, per rispondere alla domanda, no, non ci sono spese di runtime reali all'interno di una try {} finally {}struttura che non utilizza eccezioni (non è inaudita, come visto). Ciò che è probabilmente costoso è il tempo di manutenzione quando si legge il codice e si vede questo stile di codice non tipico e il programmatore deve pensare che qualcosa di diverso accade in questo metodo dopo il returnprecedente ritorno alla chiamata precedente.


Come è stato menzionato, la manutenzione è un argomento per entrambi i modi di farlo. Per la cronaca, dopo considerazione, la mia preferenza sarebbe l'approccio finale.

Considera i tempi di mantenimento dell'insegnamento a qualcuno di una nuova struttura linguistica. Vedere try {} finally {}non è qualcosa che si vede spesso nel codice Java e quindi può essere fonte di confusione per le persone. C'è un certo tempo di manutenzione per l'apprendimento di strutture un po 'più avanzate in Java rispetto a ciò che le persone conoscono.

Il finally {}blocco funziona sempre . Ed è per questo che dovresti usarlo. Considera anche il tempo di manutenzione del debug dell'approccio non finalmente quando qualcuno dimentica di includere un logout al momento giusto, o lo chiama al momento improprio, o dimentica di tornare / uscire dopo averlo chiamato in modo che venga chiamato due volte. Ci sono così tanti possibili bug con questo che l'uso di try {} finally {}rende impossibile avere.

Quando si soppesano questi due costi, in termini di manutenzione è costoso non utilizzare l' try {} finally {}approccio. Mentre le persone possono cercare quanti millisecondi frazionari o istruzioni jvm aggiuntive il try {} finally {}blocco viene confrontato con l'altra versione, si deve anche considerare le ore impiegate nel debug del modo meno ideale di affrontare la deallocazione delle risorse.

Scrivi prima il codice gestibile, e preferibilmente in modo da impedire che i bug vengano scritti in seguito.


1
Una modifica proposta da Anon diceva così: "prova / finalmente offre garanzie che non avresti altrimenti riguardo alle eccezioni di runtime. Che ti piaccia o no, qualsiasi linea è praticamente in grado di generare un'eccezione" - Io: che non è esattamente vero . La struttura fornita nella domanda non ha ulteriori eccezioni che potrebbero rallentare le prestazioni del codice. Non ci sono ulteriori impatti sulle prestazioni avvolgendo un blocco try / finally attorno al blocco rispetto al blocco non-try / finally.

2
Ciò che potrebbe essere ancora più costoso in termini di manutenzione rispetto alla comprensione di questo uso di try-finally, è dover mantenere diversi blocchi di pulizia. Ora almeno sai che se è necessaria un'ulteriore pulizia, c'è solo un posto dove metterlo.
Bart van Ingen Schenau,

@BartvanIngenSchenau Effettivamente. Un tentativo-finalmente è probabilmente il modo migliore per gestirlo, semplicemente non è quello comune di una struttura che ho visto nel codice - le persone hanno abbastanza problemi a cercare di capire provare a catturare finalmente e la gestione delle eccezioni in generale .. ma provare -finaly senza un fermo? Che cosa? Le persone non lo capiscono davvero . Come hai suggerito, è meglio capire la struttura del linguaggio piuttosto che cercare di evitarlo e fare le cose male.

Per lo più volevo far sapere che l'alternativa a provare-finalmente non è neanche gratuita. Vorrei poter dare un altro voto per la tua aggiunta sui costi.
Bart van Ingen Schenau,

5

/programming/299068/how-slow-are-java-exceptions

La risposta accettata su questa domanda mostra che il wrapping di una chiamata di funzione in un blocco try catch costa meno del 5% rispetto a una chiamata di funzione semplice. In realtà, il lancio e la cattura dell'eccezione ha comportato il runtime dell'aerostato a oltre 66 volte la chiamata di funzione nuda. Quindi, se ti aspetti che il progetto dell'eccezione venga lanciato regolarmente, proverei a evitarlo (in un codice critico delle prestazioni adeguatamente profilato), tuttavia se la situazione eccezionale è rara, non è un grosso problema.


3
"se la situazione eccezionale è rara" - beh c'è una tautologia proprio lì. Eccezionale significa raro ed è proprio così che dovrebbero essere usate le eccezioni. Non fare mai parte del flusso di progettazione o controllo.
Jesse C. Slicer,

1
Le eccezioni non sono necessariamente rare nella pratica, sono solo effetti collaterali accidentali. Ad esempio, se si dispone di un'interfaccia utente errata, le persone potrebbero causare il flusso del caso d'uso dell'eccezione più spesso del flusso normale
Esailija

2
Non ci sono eccezioni nel codice della domanda.

2
@ JesseC.Slicer Esistono lingue in cui non è raro vederle utilizzate come controllo di flusso. Ad esempio, quando si esegue l'iterazione di qualcosa in Python, l'iteratore genera un'eccezione di arresto al termine. Anche in OCaml la loro libreria standard sembra essere molto "gettata felice", si getta in un gran numero di situazioni che non sono rare (se hai una mappa e prova a cercare qualcosa che non esiste nella mappa che lancia ).
Stonemetal

@stonemetal Come fa un dict di Python, con un KeyError
Izkata

4

Invece di ottimizzare, pensa a cosa sta facendo il tuo codice.

L'aggiunta del blocco finally è un'implementazione diversa: significa che ti disconnetterai da ogni eccezione.

In particolare, se l'accesso genera un'eccezione, il comportamento nella seconda funzione sarà diverso dal primo.

Se il login e il logout non fanno effettivamente parte del comportamento della funzione, suggerirei che il metodo dovrebbe fare una cosa e una cosa bene:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

e dovrebbe essere in una funzione che è già incapsulata in un contesto che gestisce il login / logout poiché questi sembrano fondamentalmente diversi da ciò che sta facendo il tuo codice principale.

Il tuo post è taggato come Java che non conosco molto bene, ma in .net userò un blocco using per questo:

vale a dire:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

E il contesto di accesso ha un metodo Dispose che disconnetterà e pulirà le risorse.

Modifica: in realtà non ho risposto alla domanda!

Non ho prove misurabili, ma non mi aspetterei che questo sia più costoso o sicuramente non abbastanza costoso per essere degno di ottimizzazione prematura.

Lascio il resto della mia risposta perché, sebbene non risponda alla domanda, penso che sia utile per il PO.


1
Per completare la risposta Java 7 ha introdotto un'istruzione try-with-resources che sarebbe simile al usingcostrutto .net , supponendo che la risorsa in questione implementasse l' AutoCloseableinterfaccia.
haylem,

Vorrei anche rimuovere questa retvariabile, che viene assegnata due volte per essere verificata una riga in seguito. Fallo if (dosomething() == false) return;.
ott--

D'accordo, ma volevo mantenere il codice OP nel modo in cui è stato fornito.
Michael,

@ ott-- Suppongo che il codice OP sia una semplificazione del loro caso d'uso - ad esempio, potrebbero avere qualcosa di più simile ret2 = doSomethingElse(ret1), ma non è rilevante per la domanda, quindi è stato rimosso.
Izkata,

if (dosomething() == false) ...è un codice errato. Usa il più intuitivoif (!dosomething()) ...
Steffen Heil il

1

Presupposto: stai sviluppando in codice C #.

La risposta rapida è che non vi è alcun impatto significativo sulle prestazioni dall'uso dei blocchi try / finally. Si verifica un colpo di scena quando si generano e si rilevano eccezioni.

La risposta più lunga è vedere di persona. Se osservi il codice CIL sottostante generato ( ad es. Riflettore red-gate, puoi vedere le istruzioni CIL sottostanti generate e vedere l'impatto in quel modo.


11
La domanda è taggata java .
Joachim Sauer,

Ben individuato! ;)
Michael Shaw,

3
Inoltre, i metodi non sono in maiuscolo, anche se potrebbe essere solo un buon gusto.
Erik Reppen,

comunque una buona risposta se la domanda era c # :)
PhillyNJ

-2

Come altri hanno già notato, il codice Ttese avrà risultati diversi, se entrambi dosomethingelseo genererà dootherstuffun'eccezione.

E sì, qualsiasi chiamata di funzione può generare un'eccezione, almeno una StackOVerflowError! Sebbene raramente sia possibile gestirli correttamente (poiché qualsiasi chiamata funzione di pulizia avrà probabilmente lo stesso problema, in alcuni casi (come ad esempio l'implementazione di blocchi) è fondamentale persino gestirlo correttamente ...


2
questo non sembra offrire nulla di sostanziale rispetto alle precedenti 6 risposte
moscerino del
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.