Mikrobankmarking Java-val

1. Bemutatkozás

Ez a rövid cikk a JMH-ra (a Java Microbenchmark Harness) koncentrál. Először megismerkedünk az API-val és megtanuljuk annak alapjait. Ezután néhány bevált gyakorlatot látnánk, amelyeket figyelembe kell vennünk a mikrobarátok írásakor.

Egyszerűen fogalmazva, a JMH gondoskodik az olyan dolgokról, mint a JVM bemelegítési és kódoptimalizálási útvonalai, a benchmarkolást a lehető legegyszerűbbé téve.

2. Az első lépések

A kezdéshez valójában folytathatjuk a munkát a Java 8-mal, és egyszerűen meghatározhatjuk a függőségeket:

 org.openjdk.jmh jmh-core 1.19 org.openjdk.jmh jmh-generator-annprocess 1.19 

A JMH Core és a JMH Annotation Processor legújabb verziói a Maven Central oldalon találhatók.

Ezután hozzon létre egy egyszerű benchmarkot a felhasználásával @Viszonyítási alap kommentár (bármely nyilvános osztályban):

@Benchmark public void init () {// Ne csinálj semmit}

Ezután hozzáadjuk a fő osztályt, amely elindítja az összehasonlítási folyamatot:

public class BenchmarkRunner {public static void main (String [] args) dobja a Exceptiont {org.openjdk.jmh.Main.main (args); }}

Most fut BenchmarkRunner vitathatatlanul kissé haszontalan referenciaértékünket hajtja végre. A futtatás befejezése után összefoglaló táblázat jelenik meg:

# Futtatás kész. Teljes idő: 00:06:45 Benchmark Mode Cnt Score Error Units BenchMark.init thrpt 200 3099210741.962 ± 17510507.589 ops / s

3. A referenciaértékek típusai

A JMH néhány lehetséges referenciaértéket támogat: Átbocsátás,Átlagos idő,SampleTime, és SingleShotTime. Ezek a következőn keresztül konfigurálhatók: @BenchmarkMode kommentár:

@Benchmark @BenchmarkMode (Mode.AverageTime) public void init () {// Ne csinálj semmit}

Az így kapott táblázat átlagos időmutatóval rendelkezik (az áteresztőképesség helyett):

# Futtatás kész. Teljes idő: 00:00:40 Benchmark Mode Cnt Score Error Units BenchMark.init avgt 20 ≈ 10⁻⁹ s / op

4. A bemelegítés és a végrehajtás konfigurálása

A @Villa annotációval beállíthatjuk, hogyan történik a benchmark végrehajtás: a érték paraméter határozza meg, hogy a benchmark hányszor kerül végrehajtásra, és a bemelegítés A paraméter szabályozza, hogy egy referenciaérték hányszor fog szárazon futni az eredmények gyűjtése előtt, például:

@Benchmark @Fork (érték = 1, bemelegítés = 2) @BenchmarkMode (Mode.Throughput) public void init () {// Ne csinálj semmit}

Ez utasítja a JMH-t, hogy futtasson két bemelegítő villát, és dobja el az eredményeket, mielőtt a valós idejű benchmarkingra lépne.

Továbbá a @Bemelegítés annotációval szabályozható a bemelegedési ismétlések száma. Például, @Bemelegítés (iterációk = 5) elmondja a JMH-nak, hogy az alapértelmezett 20-zal szemben öt bemelegítő iteráció lesz elegendő.

5. Állam

Vizsgáljuk meg, hogyan lehet a hash algoritmus összehasonlításának kevésbé triviális és indikatívabb feladatát végrehajtani Állapot. Tegyük fel, hogy úgy döntünk, hogy extra védelmet adunk a szótár-támadások ellen egy jelszó-adatbázisban, ha a jelszót néhány százszor megvonjuk.

Felfedezhetjük a teljesítmény hatását az a használatával Állapot tárgy:

@State (Scope.Benchmark) nyilvános osztály ExecutionPlan {@Param ({"100", "200", "300", "500", "1000"}) nyilvános iterációk; nyilvános Hasher mormogás3; public String jelszó = "4v3rys3kur3p455w0rd"; @Setup (Level.Invocation) public void setUp () {murmur3 = Hashing.murmur3_128 (). NewHasher (); }}

A benchmark módszerünk ekkor fog kinézni:

@Fork (érték = 1, bemelegítés = 1) @Benchmark @BenchmarkMode (Mode.Throughput) public void benchMurmur3_128 (ExecutionPlan terv) {for (int i = plan.iterations; i> 0; i--) {plan.murmur3 putString (terv.jelszó, Charset.defaultCharset ()); } terv.murmur3.hash (); }

Itt a mező iterációk megfelelő értékekkel kerül kitöltésre a @Param a JMH jegyzete, amikor azt átadják a benchmark módszernek. A @Beállít az annotált metódust a referenciaérték minden egyes meghívása előtt meghívják, és létrehoz egy újat Hasher az elszigeteltség biztosítása.

Amikor a végrehajtás befejeződött, az alábbihoz hasonló eredményt kapunk:

# Futtatás kész. Teljes idő: 00:06:47 Benchmark (iterációk) Mód Cnt Pontszám Hibaegységek BenchMark.benchMurmur3_128 100 thrpt 20 92463.622 ± 1672.227 ops / s BenchMark.benchMurmur3_128 200 thrpt 20 39737.532 ± 5294.200 ops / s BenchMark.benchMr1401.0401.0401.040.31401.040.31401.040.31.140.31.140.140.31.1404.040.140.31.040.31.040.040.31.040.040.040.040.040.040.34.040.040.31.040.34.040.31.040.31.040.34.040.31.040.34.040.31.040.31.040.31.040.31.040.34.040.31.040.31.040.34.040.31.040.31.040.31.040.34.040.31.0404.0.0 ops / s BenchMark.benchMurmur3_128 500 thrpt 20 18315.211 ± 222.534 ops / s BenchMark.benchMurmur3_128 1000 thrpt 20 8960.008 ± 658.524 ops / s

6. A holtkód megszüntetése

A mikrobarka futtatásakor nagyon fontos tisztában lenni az optimalizálásokkal. Ellenkező esetben nagyon félrevezető módon befolyásolhatják a referenciaértékeket.

Hogy kicsit konkrétabbá tegyük a dolgokat, vegyünk egy példát:

@Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) public void doNothing () {} @Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) public void objectCreation; }

Arra számítunk, hogy az objektum kiosztása többe kerül, mint egyáltalán semmit sem csinálni. Ha azonban a referenciaértékeket futtatjuk:

Benchmark Mode Cnt Score Error Units BenchMark.doNothing avgt 40 0,609 ± 0,006 ns / op BenchMark.objectCreation avgt 40 0,613 ± 0,007 ns / op

Nyilvánvalóan helyet keres a TLAB-ban, egy objektum létrehozása és inicializálása szinte ingyenes! Csak ezeket a számokat megnézve tudnunk kell, hogy itt valami nem egészen összeadódik.

Itt a holt kód megszüntetésének áldozata vagyunk. A fordítók nagyon jól tudják optimalizálni a redundáns kódot. Ami azt illeti, a JIT fordítója pontosan ezt tette itt.

Ennek az optimalizálásnak a megakadályozása érdekében valahogyan be kellene csalnunk a fordítót, és el kellene gondolkodnunk, hogy a kódot valamilyen más komponens használja. Ennek egyik módja a létrehozott objektum visszaadása:

@Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) public Object pillarsOfCreation () {return new Object (); }

Ezenkívül megengedhetjük a Fekete lyuk fogyaszd:

@Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) public void blackHole (Blackhole blackhole) {blackhole.consume (new Object ()); }

Miután Fekete lyuk Az objektum Consume felhasználásával meg lehet győzni a JIT fordítót, hogy ne alkalmazza a holt kód megszüntetésének optimalizálását. Mindenesetre, ha újra elvégezzük a dolgozatokat, a számok értelmesebbek lennének:

Benchmark Mode Cnt Score Error Units BenchMark.blackHole avgt 20 4.126 ± 0.173 ns / op BenchMark.doNothing avgt 20 0.639 ± 0.012 ns / op BenchMark.objectCreation avgt 20 0.635 ± 0.011 ns / op BenchMark.pillarsOfCreation avgt 20 4.061 ± 0.037 ns

7. Állandó hajtogatás

Vegyünk még egy példát:

@Benchmark public double foldedLog () {int x = 8; return Math.log (x); }

Az állandókon alapuló számítások pontosan ugyanazt a kimenetet adhatják vissza, függetlenül a végrehajtások számától. Ezért nagyon jó esély van arra, hogy a JIT fordító a logaritmus függvényhívást az eredménnyel helyettesítse:

@Benchmark public double foldedLog () {return 2.0794415416798357; }

A részértékelésnek ezt a formáját állandó hajtogatásnak nevezzük. Ebben az esetben az állandó hajtogatás teljesen elkerüli a Math.log hívás, ami a benchmark lényege volt.

Az állandó hajtogatás megakadályozása érdekében az állandó állapotot beilleszthetjük egy állapotobjektumba:

@State (Scope.Benchmark) public static class Log {public int x = 8; } @Benchmark public double log (Log input) {return Math.log (input.x); }

Ha ezeket a referenciaértékeket egymás ellen vetjük össze:

Benchmark Mode Cnt Score Hibaegységek BenchMark.foldedLog thrpt 20 449313097.433 ± 11850214.900 op / s BenchMark.log thrpt 20 35317997.064 ± 604370.461 op / s

Nyilvánvalóan a napló A benchmark komoly munkát végez a hajtogatottLog, ésszerű.

8. Következtetés

Ez az oktatóanyag a Java mikro-benchmarking hámjaira összpontosított és azokat bemutatta.

Mint mindig, a GitHubon is megtalálhatók kódpéldák.