TESTOWANIE WYDAJNOŚCI KODU ZA POMOCĄ NARZĘDZIA JMH WOJCIECH OCZKOWSKI O CZYM BĘDZIEMY MÓWIĆ • Czym są benchmarki i w czym mogą nam pomóc • Jakie są problemy z mierzeniem wydajności kodu Javy • Jak JMH pozwala uniknąć typowych błędów • Praktyczne przykłady WOJCIECH OCZKOWSKI • >> 20 lat programowania dla przyjemności • >> 10 lat zawodowego programowania w Javie • Branże: Obronna, telco / call-center, finansowa • Szkolenia • Wydajność, Architektura, Integracja • Właściciel IT Kontekst • Lider Bydgoszcz JUG • Aktywny członek Toruń JUG • Ojciec, mąż, żeglarz BENCHMARKI • Porównywanie wydajności alternatywnych rozwiązań • Sprawdzanie wydajności bez konieczności budowy całego rozwiązania • Eksperymentowanie z nowymi rozwiązaniami • Tuning CO W TYM TRUDNEGO? long start = System.currentTimeMillis(); work(); System.out.println( System.currentTimeMillis() - start); WHAT COULD POSSIBLY GO WRONG? POMIAR CZASU • Ziarnistość pomiaru czasu (~30 ns Linux, ~300 ns Windows [1t]) • Ukryty narzut System.nanoTime(); • Błąd pomiaru • różnice w implementacji timerów w systemach operacyjnych WHAT COULD POSSIBLY GO WRONG? OPTYMALIZACJE KOMPILATORA - PĘTLE • Rozwijanie pętli • Piplineing long start = System.nanoTime(); for (int i = 0; i < 100000; i++) { work(); } System.out.println((System.nanoTime() - start)/100000); WHAT COULD POSSIBLY GO WRONG? OPTYMALIZACJE KOMPILATORA – DEAD CODE ELIMINATION public void measuredMethod(){ // start measurement int result = work(); // end of measurement } WHAT COULD POSSIBLY GO WRONG? OPTYMALIZACJE KOMPILATORA – CONSTANT FOLDING public int measuredMethod(){ // start measurement return 42 + work(); // end of measurement } WHAT COULD POSSIBLY GO WRONG? FALSE SHARING int someCounter; int completelyDifferentCounter; // on Thread 1 public int measuredMethod1(){ return work(someCounter); } // on Thread 2 public int measuredMethod2(){ return work(completelyDifferentCounter); } WHAT COULD POSSIBLY GO WRONG? WARM UP • -XX:CompileThreshold=[1500,2000,10000] • -XX:-PrintCompilation • -XX:MaxInlineSize=35 • -XX:+PrintInlining • -XX:LoopUnrollLimit=n WHAT COULD POSSIBLY GO WRONG? RÓŻNICE SYSTEMÓW OPERACYJNYCH • Różnice w implementacji JVM • Różnice timerów • Różnice scheduler’ów • 32 vs 64 bit • Niektóre optymalizacje dostępne są dla wybranych OS’ów • Niektóre bug’i też ;) CZYM JEST JMH I JAK MOŻE POMÓC • Narzędzie do budowania benchmarków • Pomaga w uniknięciu typowych problemów • Nie zwalnia z myślenia o nich. • Java -> org.openjdk.jmh:jmh-java-benchmark-archetype • Scala -> org.openjdk.jmh:jmh-scala-benchmark-archetype -> sbt-jmh plugin • Groovy -> org.openjdk.jmh:jmh-groovy-benchmark-archetype • Kotlin -> org.openjdk.jmh:jmh-java-benchmark-archetype NOWY BENCHMARK package pl.itkontekst.jmhtest; import org.openjdk.jmh.annotations.Benchmark; public class MyBenchmark { @Benchmark public void testMethod(){ } } # # # # # # # # # # JMH 1.14.1 (released 13 days ago) VM version: JDK 1.8.0_77, VM 25.77-b03 VM invoker: C:\Program Files\Java\jre1.8.0_77\bin\java.exe VM options: <none> Warmup: 20 iterations, 1 s each Measurement: 20 iterations, 1 s each Timeout: 10 min per iteration Threads: 1 thread, will synchronize iterations Benchmark mode: Throughput, ops/time Benchmark: pl.itkontekst.jmhtest.MyBenchmark.testMethod # Run progress: 0,00% complete, ETA 00:06:40 # Fork: 1 of 10 # Warmup Iteration 1: 3321638210,400 ops/s # Warmup Iteration 2: 3035709856,963 ops/s … Iteration 19: 3292590873,371 ops/s Iteration 20: 3352591339,138 ops/s Result "testMethod": 3336940878,008 ?(99.9%) 21593035,964 ops/s [Average] (min, avg, max) = (2837336846,716, 3336940878,008, 3433712311,191), stdev = 91426266,353 CI (99.9%): [3315347842,045, 3358533913,972] (assumes normal distribution) # Run complete. Total time: 00:06:43 Benchmark MyBenchmark.testMethod Mode thrpt Cnt 200 Score Error 3336940878,008 ? 21593035,964 Units ops/s OPCJE POMIARÓW @Fork(1) @Warmup(iterations = 5) @Measurement(iterations = 5) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class MyBenchmark { @Benchmark public void testMethod() { } } KLASY STANU, SETUP, PARAMETRY, BLACK HOLE @State(Scope.Benchmark) public static class MyState { @Param({"1","2","3"}) int value; @Setup public void init(){} } @Benchmark public void testAdd(Blackhole blackhole,MyState state) { blackhole.consume(state.value+state.value); Blackhole.consumeCPU(10); } @Benchmark public void testMethod(Blackhole blackhole) { Blackhole.consumeCPU(10); } WĄTKI, GRUPY, PADDING @Threads(4) public class MyBenchmark { @State(Scope.Benchmark) public static class MyState { @Param({"2"}) int value; } @State(Scope.Benchmark) public static class MyState2 { @Param({"1"}) int value2; } @Benchmark @Group("add") public void testAdd(Blackhole blackhole,MyState state) { blackhole.consume(state.value+state.value); } @Benchmark @Group("add") public void testAdd2(Blackhole blackhole,MyState2 state) { blackhole.consume(state.value2+state.value2); } } KONTROLA PRACY KOMPILATORA @CompilerControl(CompilerControl.Mode.DONT_INLINE) public void testDontInline(){} @CompilerControl(CompilerControl.Mode.INLINE) public void testForceInline(){} @CompilerControl(CompilerControl.Mode.EXCLUDE) public void testDontCompile(){} PROGRAMOWA KONTROLA URUCHAMIANIA public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(MyBenchmark.class.getSimpleName()) .warmupIterations(1) .measurementIterations(3) .forks(1) .build(); new Runner(opt).run(); } PROFILERY java -jar benchmarks.jar -lprof Supported profilers: cl: Classloader profiling via standard MBeans comp: JIT compiler profiling via standard MBeans gc: GC profiling via standard MBeans hs_cl: HotSpot (tm) classloader profiling via implementation-specific MBeans hs_comp: HotSpot (tm) JIT compiler profiling via implementation-specific MBeans hs_gc: HotSpot (tm) memory manager (GC) profiling via implementation-specific MBeans hs_rt: HotSpot (tm) runtime profiling via implementation-specific MBeans hs_thr: HotSpot (tm) threading subsystem via implementation-specific MBeans pauses: Pauses profiler perf: Linux perf Statistics perfasm: Linux perf + PrintAssembly Profiler perfnorm: Linux perf statistics, normalized by operation count stack: Simple and naive Java stack profiler Unsupported profilers: xperfasm: <none> PROFILERY Stack profiler: ....[Thread state distributions].................................................................... 97,2% RUNNABLE 2,8% WAITING ....[Thread state: RUNNABLE]........................................................................ 56,8% 58,5% pl.itkontekst.jmhtest.MyBenchmark.testAdd 39,8% 41,0% pl.itkontekst.jmhtest.generated.MyBenchmark_add_jmhTest.testAdd_thrpt_jmhStub 0,3% 0,3% sun.misc.Unsafe.compareAndSwapInt 0,1% 0,1% java.lang.Thread.currentThread 0,1% 0,1% sun.misc.Unsafe.unpark ....[Thread state: WAITING]......................................................................... 2,8% 100,0% sun.misc.Unsafe.park WYNIKI java -jar benchmarks.jar -lrf Available formats: text, csv, scsv, json, latex java -jar benchmarks.jar -rff output.csv PODSUMOWANIE • Pisanie Benchmarków nie jest trywialne • JMH pomaga w unikaniu typowych problemów ale nie zwalnia z myślenia o nich • JMH nie zastąpi ostatecznych testów wydajnościowych • JIT to potężny oręż, który może Ci obciąć rękę kiedy nie będziesz uważny WARTO POSŁUCHAĆ / POCZYTAĆ JMH Samples http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmhsamples/src/main/java/org/openjdk/jmh/samples/ • Aleksey Shipilëv – dzieła wybrane • Jarosław Pałka – dzieła wybrane • Charlie Hunt – dzieła wybrane • Scott Oaks – dzieła wybrane • Kirk Pepperdine – dzieła wybrane PYTANIA?