Učinci izvedbe iznimaka u Javi

1. Pregled

U Javi se iznimke općenito smatraju skupima i ne bi se trebale koristiti za kontrolu protoka. Ovaj vodič dokazati će da je ta percepcija ispravna i odrediti što uzrokuje problem izvedbe.

2. Postavljanje okruženja

Prije pisanja koda za procjenu troškova izvedbe, moramo postaviti okruženje za mjerenje.

2.1. Kabelski svežanj Java Microbenchmark

Mjerenje režijskih troškova iznimki nije tako lako kao izvršavanje metode u jednostavnoj petlji i bilježenje ukupnog vremena.

Razlog je taj što pravovremeni kompajler može stati na put i optimizirati kod. Takva optimizacija može učiniti da kod radi bolje nego što bi to zapravo učinio u proizvodnom okruženju. Drugim riječima, moglo bi donijeti lažno pozitivne rezultate.

Da bismo stvorili kontrolirano okruženje koje može ublažiti JVM optimizaciju, upotrijebit ćemo Java Microbenchmark Harness ili JMH ukratko.

Sljedeći pododjeljci proći će kroz postavljanje okruženja za mjerenje bez ulaska u detalje JMH. Za više informacija o ovom alatu, pogledajte našu lekciju Microbenchmarking with Java.

2.2. Dobivanje JMH artefakata

Da biste dobili JMH artefakte, dodajte ove dvije ovisnosti u POM:

 org.openjdk.jmh jmh-core 1,21 org.openjdk.jmh jmh-generator-annprocess 1,21 

Molimo pogledajte Maven Central za najnovije verzije JMH Core i JMH Annotation Processor.

2.3. Referentna klasa

Trebat će nam razred za održavanje mjerila:

@Fork (1) @Warmup (iteracije = 2) @Measurement (iterations = 10) @BenchmarkMode (Mode.AverageTime) @OutputTimeUnit (TimeUnit.MILLISECONDS) javna klasa ExceptionBenchmark {private static final int LIMIT = 10_000; // mjerila idu ovdje}

Prođimo kroz JMH napomene prikazane gore:

  • @Fork: Određivanje koliko puta JMH mora iznjedriti novi postupak da bi pokrenuo referentne vrijednosti. Postavili smo njegovu vrijednost na 1 da generiramo samo jedan postupak, izbjegavajući predugo čekanje da bismo vidjeli rezultat
  • @Zagrijati se: Nošenje parametara zagrijavanja. The ponavljanja element 2 znači da se prva dva izvođenja zanemaruju pri izračunavanju rezultata
  • @Mjerenje: Nošenje mjernih parametara. An ponavljanja vrijednost 10 znači da će JMH izvršiti svaku metodu 10 puta
  • @BenchmarkMode: Ovako bi JHM trebao prikupljati rezultate izvršenja. Vrijednost Prosječno vrijeme zahtijeva od JMH da broji prosječno vrijeme potrebno metodi za dovršavanje operacija
  • @OutputTimeUnit: Označava izlaznu vremensku jedinicu, koja je u ovom slučaju milisekunda

Osim toga, postoji statičko polje unutar tijela klase, naime OGRANIČITI. Ovo je broj ponavljanja u svakom tijelu metode.

2.4. Izvršenje mjerila

Da bismo izvršili mjerila, potreban nam je glavni metoda:

javna klasa MappingFrameworksPerformance {public static void main (String [] args) baca iznimku {org.openjdk.jmh.Main.main (args); }}

Projekt možemo spakirati u JAR datoteku i pokrenuti ga u naredbenom retku. Ako to učinite sada, naravno, stvorit će se prazan izlaz jer nismo dodali nijednu metodu usporedbe.

Radi praktičnosti možemo dodati maven-jar-plugin na POM. Ovaj nam dodatak omogućuje izvršavanje glavni metoda unutar IDE-a:

org.apache.maven.plugins maven-jar-plugin 3.2.0 com.baeldung.performancetests.MappingFrameworksPerformance 

Najnovija verzija maven-jar-plugin možete pronaći ovdje.

3. Mjerenje performansi

Vrijeme je da postoje neke metode mjerenja performansi za mjerenje učinka. Svaka od ovih metoda mora sadržavati @Benchmark bilješka.

3.1. Metoda se normalno vraća

Počnimo s metodom koja se normalno vraća; odnosno metoda koja ne donosi iznimku:

@Benchmark javna praznina doNotThrowException (Blackhole blackhole) {for (int i = 0; i <LIMIT; i ++) {blackhole.consume (new Object ()); }}

The Crna rupa parametar upućuje na instancu Crna rupa. Ovo je JMH klasa koja pomaže u sprečavanju uklanjanja mrtvog koda, optimizaciju koju može izvršiti upravo pravodobni prevodilac.

Referentna vrijednost, u ovom slučaju, ne donosi iznimku. Zapravo, koristit ćemo ga kao referencu za procjenu izvedbe onih koji bacaju iznimke.

Izvršenje glavni metoda dat će nam izvještaj:

Benchmark Mode Cnt Score Greška Jedinice Jedinica ExceptionBenchmark.doNotThrowException prosjek 10 0,049 ± 0,006 ms / op

U ovom rezultatu nema ništa posebno. Prosječno vrijeme izvršavanja referentne vrijednosti je 0,049 milisekundi, što je samo po sebi prilično besmisleno.

3.2. Stvaranje i bacanje iznimke

Evo još jednog mjerila koje donosi i uhvaća iznimke:

@Benchmark javna praznina throwAndCatchException (Blackhole blackhole) {for (int i = 0; i <LIMIT; i ++) {try {throw new Exception (); } catch (Iznimka e) {blackhole.consume (e); }}}

Pogledajmo izlaz:

Benchmark Mode Cnt Rezultat Jedinice pogrešaka Jedinice ExceptionBenchmark.doNotThrowException avgt 10 0,048 ± 0,003 ms / op ExceptionBenchmark.throwAndCatchException avgt 10 17,942 ± 0,846 ms / op

Mala promjena u vremenu izvršenja metode doNotThrowException nije važno. To je samo fluktuacija stanja osnovnog OS-a i JVM-a. Ključno za poneti je to izbacivanjem iznimke metoda se pokreće stotinama puta sporije.

Sljedećih nekoliko pododjeljaka saznat će što točno dovodi do tako dramatične razlike.

3.3. Stvaranje iznimke bez bacanja

Umjesto da stvorimo, bacimo i uhvatimo iznimku, mi ćemo je samo stvoriti:

@Benchmark javna praznina createExceptionWithoutThrowingIt (Blackhole blackhole) {for (int i = 0; i <LIMIT; i ++) {blackhole.consume (nova iznimka ()); }}

Izvršimo sada tri referentna mjerila:

Benchmark Mode Cnt Score Greška Jedinice Jedinice ExceptionBenchmark.createExceptionWithoutThrowingIt avgt 10 17.601 ± 3.152 ms / op ExceptionBenchmark.doNotThrowException avgt 10 0,054 ± 0,014 ms / op ExceptionBenchmark.throwAndCatchException opg74 ± 0,07 ms

Rezultat može iznenaditi: vrijeme izvršavanja prve i treće metode gotovo je isto, dok je vrijeme izvršavanja druge znatno manje.

U ovom trenutku je jasno da the bacanje i ulov same izjave prilično su jeftine. Stvaranje iznimaka, s druge strane, stvara velike režijske troškove.

3.4. Bacanje iznimke bez dodavanja traga stoga

Otkrijmo zašto je konstrukcija iznimke mnogo skuplja od izvođenja običnog objekta:

@Benchmark @Fork (value = 1, jvmArgs = "-XX: -StackTraceInThrowable") javna praznina throwExceptionWithoutAddingStackTrace (Blackhole blackhole) {for (int i = 0; i <LIMIT; i ++) {try {throw new Exception (); } catch (Iznimka e) {blackhole.consume (e); }}}

Jedina razlika između ove metode i one u pododjeljku 3.2 je jvmArgs element. Njegova vrijednost -XX: -StackTraceInThrowable je JVM opcija, čuvajući praćenje steka da se ne doda iznimci.

Ponovno pokrenimo mjerila:

Benchmark Mode Cnt Rezultat pogreške Jedinice Jedinice ExceptionBenchmark.createExceptionWithoutThrowingIt avgt 10 17.874 ± 3.199 ms / op ExceptionBenchmark.doNotThrowException avgt 10 0.046 ± 0.003 ms / op ExceptionBenchmark.throwAndCatchException 0.0gcet 0.0Gsec 0.0

Nepunjavanjem iznimke tragom steka smanjili smo trajanje izvršenja za više od 100 puta. Izgleda, hodajući kroz stog i dodajući njegove okvire iznimci dovodi do tromosti koju smo vidjeli.

3.5. Bacanje iznimke i odmotavanje njezinog traga stoga

Napokon, pogledajmo što će se dogoditi ako bacimo iznimku i odmotamo trag steka kad je hvatamo:

@Benchmark javna praznina throwExceptionAndUnwindStackTrace (Blackhole blackhole) {for (int i = 0; i <LIMIT; i ++) {try {throw new Exception (); } catch (Iznimka e) {blackhole.consume (e.getStackTrace ()); }}}

Evo ishoda:

Benchmark Mode Cnt Score Error Units ExceptionBenchmark.createExceptionWithoutThrowingIt avgt 10 16,605 ± 0,988 ms / op ExceptionBenchmark.doNotThrowException avgt 10 0,047 ± 0,006 ms / op ExceptionBenchmark.throwAndCatchException 10Go. ExceptionBenchmark.throwExceptionWithoutAddingStackTrace prosj. 10 1,185 ± 0,015 ms / op

Samo odmotavanjem traga stoga, vidimo nevjerojatan porast od oko 20 puta u trajanju izvršenja. Drugim riječima, izvedba je puno gora ako uz izbacivanje izvučemo i trag stoga iz iznimke.

4. Zaključak

U ovom vodiču analizirali smo učinke izuzetaka na izvedbu. Točnije, otkrilo je da su troškovi izvedbe uglavnom dodatak tragu stoga iznimci. Ako se ovaj trag stoga nakon toga odmota, režijski troškovi postaju mnogo veći.

Budući da je izbacivanje i rukovanje iznimkama skupo, ne bismo ga trebali koristiti za normalne tijekove programa. Umjesto toga, kao što naziv govori, iznimke se trebaju koristiti samo u iznimnim slučajevima.

Kompletni izvorni kod možete pronaći na GitHubu.


$config[zx-auto] not found$config[zx-overlay] not found