Testowanie Wydajno*ci Kodu za pomoc* Narz*dzia JMH

advertisement
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?
Download