SlideShare ist ein Scribd-Unternehmen logo
1 von 62
Что же мы измеряем?
Валеев Тагир
Институт систем информатики СО РАН
1
Disclaimer
Я
могу
врать!
2
StreamEx
https://github.com/amaembo/streamex
3
Насколько Stream API медленнее?
static long sumTwice(int max) {
long sum = 0;
for(int i=1; i<=max; i++) sum+=i*2;
return sum;
}
4
static long sumTwiceStream(int max) {
return IntStream.rangeClosed(1, max)
.mapToLong(x -> x*2).sum();
}
Спрашивает StackOverflow
5
http://stackoverflow.com/q/31761271/4856258 (удалено автором)
Predicate<Integer> в Java 8 быстрее, чем IntPredicate
В Java 8 предлагается использовать IntPredicate вместо Predicate<Integer>и
аналогично для других примитивных типов, так как таким образом можно
избавиться от накладных расходов на автобоксинг, но когда я запускаю
нижеследующий код, я получаю совершенно противоположный результат:
на моей системе IntPredicate в 30-50 раз медленее, чем Predicate.
In Java 8 Predicate<Integer> is faster than IntPredicate
In Java 8 it is suggested that we should use IntPredicate rather than Predicate<Integer> and same for
other premitive types as former one reduces the overhead related to autoboxing but when i run the
following code. I get results shockingly opposite as IntPredicate is 30-50 times slower than Predicate
on my system.
Спрашивает StackOverflow
6
Long start = System.currentTimeMillis();
IntPredicate evenNumPredicate = (int i) -> i % 2 == 0;
evenNumPredicate.test(1000);
System.out.println(System.currentTimeMillis()-start);
start = System.currentTimeMillis();
Predicate<Integer> evenNumPredicate1 =
(Integer i) -> i % 2 == 0;
evenNumPredicate1.test(1000);
System.out.println(System.currentTimeMillis()-start);
Реакция сообщества
Это потрясающе плохой бенчмарк.
В него можно внести несколько
кардинальных улучшений, и это
всё равно будет плохой бенчмарк.
– Marko Topolnik
This benchmark is shockingly bad. It could be
improved in several significant ways and still be a
bad benchmark.
7
Наивняк
public static void main(String[] args) {
long startSimple = System.nanoTime();
long resultSimple = sumTwice(10_000_000);
long endSimple = System.nanoTime();
System.out.printf("Simple: %d; time=%8.3fms%n",
resultSimple, (endSimple-startSimple)/1_000_000.0);
long startStream = System.nanoTime();
long resultStream = sumTwiceStream(10_000_000);
long endStream = System.nanoTime();
System.out.printf("Stream: %d; time=%8.3fms%n",
resultStream, (endStream-startStream)/1_000_000.0);
}
8
Результаты наивняка
9
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
-Xint
Simple: 100000010000000; time= 136.347ms
Stream: 100000010000000; time=1647.296ms
-XX:-UseOnStackReplacement
Simple: 100000010000000; time= 150.321ms
Stream: 100000010000000; time= 374.367ms
-XX:-UseOnStackReplacement -XX:-UseLoopCounter
Simple: 100000010000000; time= 136.584ms
Stream: 100000010000000; time= 364.105ms
Насколько Stream API медленнее?
static long sumTwice(int max) {
long sum = 0;
for(int i=1; i<=max; i++) sum+=i*2;
return sum;
}
10
static long sumTwiceStream(int max) {
return IntStream.rangeClosed(1, max)
.mapToLong(x -> x*2).sum();
}
Интерпретатор и JIT-компилятор
11
Результаты наивняка
12
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
-Xint
Simple: 100000010000000; time= 136.347ms
Stream: 100000010000000; time=1647.296ms
-XX:-UseOnStackReplacement
Simple: 100000010000000; time= 150.321ms
Stream: 100000010000000; time= 374.367ms
-XX:-UseOnStackReplacement -XX:-UseLoopCounter
Simple: 100000010000000; time= 136.584ms
Stream: 100000010000000; time= 364.105ms
On-Stack Replacement (OSR)
Замена на стеке
13
Результаты наивняка
14
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
-Xint
Simple: 100000010000000; time= 136.347ms
Stream: 100000010000000; time=1647.296ms
-XX:-UseOnStackReplacement
Simple: 100000010000000; time= 150.321ms
Stream: 100000010000000; time= 374.367ms
-XX:-UseOnStackReplacement -XX:-UseLoopCounter
Simple: 100000010000000; time= 136.584ms
Stream: 100000010000000; time= 364.105ms
Результаты наивняка
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
-Xint
Simple: 100000010000000; time= 136.347ms
Stream: 100000010000000; time=1647.296ms
-XX:-UseOnStackReplacement
Simple: 100000010000000; time= 150.321ms
Stream: 100000010000000; time= 374.367ms
-XX:-UseOnStackReplacement -XX:-UseLoopCounter
Simple: 100000010000000; time= 136.584ms
Stream: 100000010000000; time= 364.105ms
15
Stream-операции
16
return IntStream
.rangeClosed(1, max) // создание Stream
.mapToLong(x -> x*2) // промежуточная операция
.sum(); // конечная операция
Иерархия вызовов Stream
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
17
Иерархия вызовов Stream
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
18
МАГИЯ
Иерархия вызовов Stream
19
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
МАГИЯ
Результаты наивняка
20
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
-Xint
Simple: 100000010000000; time= 136.347ms
Stream: 100000010000000; time=1647.296ms
-XX:-UseOnStackReplacement
Simple: 100000010000000; time= 150.321ms
Stream: 100000010000000; time= 374.367ms
-XX:-UseOnStackReplacement -XX:-UseLoopCounter
Simple: 100000010000000; time= 136.584ms
Stream: 100000010000000; time= 364.105ms
JMH
• Инструкция и примеры здесь:
http://openjdk.java.net/projects/code-tools/jmh/
• $ mvn archetype:generate 
-DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh 
-DarchetypeArtifactId=jmh-java-benchmark-archetype 
-DgroupId=org.sample -DartifactId=test -Dversion=1.0
• $ mvn clean install
• pom.xml: <javac.target>1.6</javac.target>
21
JMH benchmark
public class MyBenchmark {
@Benchmark
public long stream() {
return sumTwiceStream(10_000_000);
}
@Benchmark
public long simple() {
return sumTwice(10_000_000);
}
...
}
22
JMH benchmark
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(5)
@State(Scope.Benchmark)
public class MyBenchmark {
...
}
[jmhtest/target]$ java –jar benchmark.jar >out.txt
23
JMH benchmark – результаты
# JMH 1.11.1 (released 7 days ago)
# VM version: JDK 1.8.0_60, VM 25.60-b23
...
Benchmark Mode Cnt Score Error Units
MyBenchmark.simple avgt 50 4.535 ± 0.009 ms/op
MyBenchmark.stream avgt 50 4.123 ± 0.009 ms/op
____________________________________________________________
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
24
JMH benchmark – результаты
# JMH 1.11.1 (released 7 days ago)
# VM version: JDK 1.8.0_60, VM 25.60-b23
...
Benchmark Mode Cnt Score Error Units
MyBenchmark.simple avgt 50 4.535 ± 0.009 ms/op
MyBenchmark.stream avgt 50 4.123 ± 0.009 ms/op
____________________________________________________________
Simple: 100000010000000; time= 8.286ms
Stream: 100000010000000; time= 57.774ms
25
JMH – параметризуем
@Param({"100000", "1000000", "10000000"})
private int n;
@Benchmark
public long stream() {
return sumTwiceStream(n);
}
@Benchmark
public long simple() {
return sumTwice(n);
}
26
JMH с параметром – результаты
Benchmark (n) Score Error Units
MyBenchmark.simple 100000 0.048 ± 0.001 ms/op
MyBenchmark.simple 1000000 0.442 ± 0.001 ms/op
MyBenchmark.simple 10000000 4.608 ± 0.012 ms/op
MyBenchmark.stream 100000 0.567 ± 0.002 ms/op
MyBenchmark.stream 1000000 5.776 ± 0.025 ms/op
MyBenchmark.stream 10000000 4.079 ± 0.012 ms/op
27
n = 1_000_000
# Warmup Iteration 1: 0.446 ms/op
# Warmup Iteration 2: 0.414 ms/op
# Warmup Iteration 3: 1.144 ms/op
# Warmup Iteration 4: 5.729 ms/op
# Warmup Iteration 5: 5.792 ms/op
Iteration 1: 5.821 ms/op
Iteration 2: 5.751 ms/op
Iteration 3: 5.733 ms/op
...
Iteration 9: 5.787 ms/op
Iteration 10: 5.829 ms/op
28
n = 10_000_000
# Warmup Iteration 1: 4.342 ms/op
# Warmup Iteration 2: 4.132 ms/op
# Warmup Iteration 3: 4.131 ms/op
# Warmup Iteration 4: 4.113 ms/op
# Warmup Iteration 5: 4.083 ms/op
Iteration 1: 4.134 ms/op
Iteration 2: 4.121 ms/op
Iteration 3: 4.127 ms/op
...
Iteration 9: 4.099 ms/op
Iteration 10: 4.124 ms/op
29
n = 10_000_000
[jmhtest/target]$ java –jar benchmark.jar –i 20 >out.txt
# Warmup Iteration 1: 4.360 ms/op
# Warmup Iteration 2: 4.135 ms/op
...
# Warmup Iteration 5: 4.084 ms/op
Iteration 1: 4.131 ms/op
Iteration 2: 4.111 ms/op
...
Iteration 16: 4.114 ms/op
Iteration 17: 4.114 ms/op
Iteration 18: 19.181 ms/op
Iteration 19: 57.917 ms/op
Iteration 20: 57.899 ms/op
30
Наивняк с циклом
public static void main(String[] args) {
for(int i=0; i<6000; i++) {
long start = System.nanoTime();
long result = sumTwiceStream(10_000_000);
long end = System.nanoTime();
System.out.printf("#%d: %d; time=%8.3fms%n", i,
result, (end-start)/1_000_000.0);
}
}
31
Наивняк с циклом – результат
#0: 100000010000000; time= 63.484ms
#1: 100000010000000; time= 10.328ms
#2: 100000010000000; time= 3.931ms
#3: 100000010000000; time= 4.051ms
…
#5630: 100000010000000; time= 3.938ms
#5631: 100000010000000; time= 4.154ms
#5632: 100000010000000; time= 4.150ms
#5633: 100000010000000; time= 3.955ms
#5634: 100000010000000; time= 4.011ms
#5635: 100000010000000; time= 58.184ms
#5636: 100000010000000; time= 58.058ms
#5637: 100000010000000; time= 57.024ms
32
-XX:+PrintCompilation
77 1 3 java.lang.String::equals (81 bytes)
78 2 3 java.lang.String::hashCode (55 bytes)
...
80 13 n 0 java.lang.System::arraycopy (native) (static)
...
81 16 s 3 java.lang.StringBuffer::append (13 bytes)
...
106 68 ! 3 java.lang.ref.ReferenceQueue::poll (28 bytes)
...
141 221 % 4 ...$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes)
144 219 % 3 ...$RangeIntSpliterator::forEachRemaining @ -2 (65 bytes)
made not entrant
33
-XX:+PrintCompilation: читаем лог
148 221 % 4 ...::forEachRemaining @ 34 (65 bytes)
tstamp compile_id attrs comp_level name [@ osr_pos] (size) [status]
• tstamp – время в миллисекундах с начала выполнения
• compile_id – номер задачи на компиляцию в очереди
• attrs – атрибуты
• comp_level – номер уровня tier-компиляции
• name – имя класса и метода
• osr_pos – позиция в байткоде, на которой выполняется OSR
• size – размер байткода метода в байтах (или “native”)
• status – дополнительная информация о событии
34
-XX:+PrintCompilation: атрибуты
148 221 % 4 ...::forEachRemaining @ 34 (65 bytes)
tstamp compile_id attrs comp_level name [@ osr_pos] (size) [status]
• n – обёртка для native-метода (по факту не компиляция)
• % – on-stack replacement
• s – метод объявлен synchronized
• ! – есть обработчик исключений
• b – компиляция блокирует выполнение
35
-XX:+PrintCompilation: comp_level
148 221 % 4 ...::forEachRemaining @ 34 (65 bytes)
tstamp compile_id attrs comp_level name [@ osr_pos] (size) [status]
• 0 – none (интерпретатор / native-wrapper)
• 1 – simple (C1-компилятор)
• 2 – limited_profile (С1-компилятор с подсчётом вызовов и итераций циклов)
• 3 – full_profile (уровень 2 плюс профилирование типов)
• 4 – C2-компилятор
36
С1 С2Интерпретатор С2
-XX:+PrintCompilation: status
141 221 % 4 ...::forEachRemaining @ 34 (65 bytes)
144 219 % 3 ...::forEachRemaining @ -2 (65 bytes) made not entrant
37
Вход воспрещён
-XX:+PrintCompilation: status
• made zombie – метод не используется,
готов к удалению
38
-XX:+TraceNMethodInstalls
nmethod — JIT-компилированный метод (OSR или обычный)
-XX:+UnlockDiagnosticVMOptions
59 1 3 java.lang.String::hashCode (55 bytes)
Installing method (3) java.lang.String.hashCode()I
59 2 3 java.lang.String::equals (81 bytes)
Installing method (3) java.lang.String.equals(Ljava/lang/Object;)Z
60 3 3 java.lang.String::indexOf (70 bytes)
60 6 n 0 java.lang.System::arraycopy (native) (static)
Installing method (3) java.lang.String.indexOf(II)I
61 4 3 java.lang.Object::<init> (1 bytes)
Installing method (3) java.lang.Object.<init>()V
39
-XX:+PrintCompilation -XX:+TraceNMethodInstalls
#5630: 100000010000000; time= 3.896ms
22939 567 4 Test::sumTwiceStream (21 bytes)
#5631: 100000010000000; time= 4.327ms
#5632: 100000010000000; time= 4.029ms
#5633: 100000010000000; time= 4.170ms
22954 474 3 Test::sumTwiceStream (21 bytes) made not entrant
Installing method (4) Test.sumTwiceStream(I)J
#5634: 100000010000000; time= 4.072ms
22956 568 4 j.u.s.LongPipeline$$...::applyAsLong (6 bytes)
22956 204 3 j.u.s.LongPipeline$$...::applyAsLong (6 bytes) made not entrant
Installing method (4) j.u.s.LongPipeline$$Lambda$2/142257191.applyAsLong(JJ)J
#5635: 100000010000000; time= 58.784ms
40
Почему метод Test.sumTwiceStream
перекомпилировался?
-XX:Tier4InvocationThreshold=5000
-XX:Tier4BackEdgeThreshold=40000
41
Инлайнинг
42
static long sumTwice(int max) {
long sum = 0;
for(int i=1; i<=max; i++)
sum+=mult(i);
return sum;
}
static int mult(int x) {
return x*2;
}
static long sumTwice(int max) {
long sum = 0;
for(int i=1; i<=max; i++)
sum+=i*2;
return sum;
}
-XX:+PrintInlining
-XX:+PrintInlining
j.u.s.Streams$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes)
@ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot)
-> TypeProfile (55084/55084 counts) = j/u/s/IntPipeline$5$1
@ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot)
-> TypeProfile (24272/24272 counts) = Test$$Lambda$1
@ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inline (hot)
@ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot)
-> TypeProfile (24272/24272 counts) = j/u/s/ReduceOps$8ReducingSink
@ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inline (hot)
-> TypeProfile (7376/7376 counts) = j/u/s/LongPipeline$$Lambda$2
@ 2 java.lang.Long::sum (4 bytes) inline (hot)
43
-XX:+PrintInlining
j.u.s.Streams$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes)
@ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot)
-> TypeProfile (55084/55084 counts) = j/u/s/IntPipeline$5$1
@ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot)
-> TypeProfile (24272/24272 counts) = Test$$Lambda$1
@ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inline (hot)
@ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot)
-> TypeProfile (24272/24272 counts) = j/u/s/ReduceOps$8ReducingSink
@ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inline (hot)
-> TypeProfile (7376/7376 counts) = j/u/s/LongPipeline$$Lambda$2
@ 2 java.lang.Long::sum (4 bytes) inline (hot)
44
JITWatch – compile chain
45
https://github.com/AdoptOpenJDK/jitwatch
x -> x*2
Long::sum
Скомпилированный код (до 5600)
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
sumTwiceStream C1
C1
C1
C1
C1
C1
C1
C2
46
-XX:+PrintInlining
@ 12 j.u.s.Streams$RangeIntSpliterator::forEachRemaining (65 bytes) inline (hot)
@ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot)
@ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot)
-> TypeProfile (8593/8593 counts) = Test$$Lambda$1
@ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inlining too deep
@ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot)
-> TypeProfile (8593/8593 counts) = java/util/stream/ReduceOps$8ReducingSink
@ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inlining too deep
-> TypeProfile (11206/11206 counts) = java/util/stream/LongPipeline$$Lambda$2
47
JITWatch
48
Class: Test
Method: lambda$sumTwiceStream$0
JIT Compiled: Yes
Inlined: No, inlining too deep
Count: 7194
iicount: 5599
Bytes: 5
Prof factor: 1
Скомпилированный код (после 5600)
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
sumTwiceStream
C2
C2
C249
java -XX:+UnlockDiagnosticVMOptions
-XX:+PrintFlagsFinal -version
...
uintx MaxGCMinorPauseMillis = 4294967295 {product}
uintx MaxGCPauseMillis = 4294967295 {product}
uintx MaxHeapFreeRatio = 100 {manageable}
uintx MaxHeapSize := 1069547520 {product}
intx MaxInlineLevel = 9 {product}
intx MaxInlineSize = 35 {product}
intx MaxJNILocalCapacity = 65536 {product}
intx MaxJavaStackTraceDepth = 1024 {product}
intx MaxJumpTableSize = 65000 {C2 product}
...
50
JMH; -XX:MaxInlineLevel=13
Benchmark (n) Score Error Units
MyBenchmark.simple 100000 0.047 ± 0.001 ms/op
MyBenchmark.simple 1000000 0.420 ± 0.002 ms/op
MyBenchmark.simple 10000000 4.445 ± 0.029 ms/op
MyBenchmark.stream 100000 0.038 ± 0.001 ms/op
MyBenchmark.stream 1000000 0.379 ± 0.001 ms/op
MyBenchmark.stream 10000000 4.102 ± 0.014 ms/op
51
n = 1_000_000; MaxInlineLevel = 13
# Warmup Iteration 1: 0.436 ms/op
# Warmup Iteration 2: 0.408 ms/op
# Warmup Iteration 3: 0.388 ms/op
# Warmup Iteration 4: 0.376 ms/op
# Warmup Iteration 5: 0.374 ms/op
Iteration 1: 0.377 ms/op
Iteration 2: 0.375 ms/op
Iteration 3: 0.376 ms/op
...
Iteration 9: 0.375 ms/op
Iteration 10: 0.376 ms/op
52
0.446 ms/op
0.414 ms/op
1.144 ms/op
5.729 ms/op
5.792 ms/op
5.821 ms/op
5.751 ms/op
5.733 ms/op
...
5.787 ms/op
5.829 ms/op
А было так:
MaxInlineLevel=13
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
sumTwiceStream
C2
53
TypeProfile
j.u.s.Streams$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes)
@ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot)
-> TypeProfile (55084/55084 counts) = j/u/s/IntPipeline$5$1
@ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot)
-> TypeProfile (24272/24272 counts) = Test$$Lambda$1
@ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inline (hot)
@ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot)
-> TypeProfile (24272/24272 counts) = j/u/s/ReduceOps$8ReducingSink
@ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inline (hot)
-> TypeProfile (7376/7376 counts) = j/u/s/LongPipeline$$Lambda$2
@ 2 java.lang.Long::sum (4 bytes) inline (hot)
54
Profile pollution
@Param({"0", "1", "2", "3"})
private int pollute;
@Setup
public void setup() {
switch(pollute) {
case 3:
for(int i=0; i<1000; i++)
IntStream.range(0,100).mapToLong(x -> x*3).sum();
case 2:
for(int i=0; i<1000; i++)
IntStream.range(0,100).mapToLong(x -> x*4).sum();
case 1:
for(int i=0; i<1000; i++)
IntStream.range(0,100).mapToLong(x -> x*5).sum();
}
}
55
-XX:MaxInlineLevel=13 + pollution
Benchmark (n) (pollute) Score Error Units
MB.stream 100000 0 0.038 ± 0.001 ms/op
MB.stream 100000 1 0.047 ± 0.001 ms/op
MB.stream 100000 2 0.510 ± 0.003 ms/op
MB.stream 100000 3 0.509 ± 0.003 ms/op
MB.stream 1000000 0 0.371 ± 0.002 ms/op
MB.stream 1000000 1 0.435 ± 0.021 ms/op
MB.stream 1000000 2 5.385 ± 0.067 ms/op
MB.stream 1000000 3 5.433 ± 0.042 ms/op
MB.stream 10000000 0 4.029 ± 0.019 ms/op
MB.stream 10000000 1 4.550 ± 0.129 ms/op
MB.stream 10000000 2 53.658 ± 0.812 ms/op
MB.stream 10000000 3 54.563 ± 0.207 ms/op
56
MaxInlineLevel=13 + Type pollution
LongPipeline::sum
LongPipeline::reduce
AbstractPipeline::evaluate
ReduceOp::evaluateSequential
AbstractPipeline::wrapAndCopyInto
AbstractPipeline::copyInto
Spliterator.OfInt::forEachRemaining
RangeIntSpliterator::forEachRemaining (цикл)
IntPipeline$5$1::accept (mapToLong)
сгенерированный λ-класс
λ-функция x -> x*2
ReducingSink::accept (reduce)
Long::sum
сгенерированный λ-класс
sumTwiceStream
C2
C2
57
Баги в OpenJDK
• https://bugs.openjdk.java.net/browse/JDK-8015416
– tier one should collect context-dependent split profiles
• https://bugs.openjdk.java.net/browse/JDK-8015417
– profile pollution after call through invokestatic to shared
code
58
На самом деле
public static long sumTwiceOpt(int max) {
return max*(max+1L);
}
Benchmark (n) Score Error Units
MyBenchmark.opt 100000 0.003 ± 0.001 us/op
MyBenchmark.opt 1000000 0.003 ± 0.001 us/op
MyBenchmark.opt 10000000 0.003 ± 0.001 us/op
59
Но всё не зря!
-Xint
-XX:UseOnStackReplacement
-XX:UseLoopCounter
-XX:MaxInlineLevel
-XX:Tier4InvocationThreshold
-XX:Tier4BackEdgeThreshold
-XX:UnlockDiagnosticVMOptions
-XX:PrintCompilation
-XX:TraceNMethodInstalls
-XX:PrintInlining
-XX:PrintFlagsFinal
60
Опции виртуальной машины
Инструменты
JMH JITWatch
Дополнительная информация
• Алексей Шипилёв «The Black Magic of (Java) Method Dispatch»
– http://shipilev.net/blog/2015/black-magic-method-dispatch/
• Владимир Иванов «Динамическая JIT-компиляция в JVM»
– http://www.youtube.com/watch?v=oYu3HuIYDhI
• Алексей Шипилёв «Java Benchmarking: как два таймстампа
прочитать!»
– http://shipilev.net/blog/2014/nanotrusting-nanotime/
– https://www.youtube.com/watch?v=Vb3jyHl3FNk
• Пол Сандоз отвечает на «Erratic performance of
Arrays.stream().map().sum()»
– http://stackoverflow.com/a/25851390/4856258
61
Всем спасибо!
62
Пони отсюда: http://fire-seeker.deviantart.com/

Weitere ähnliche Inhalte

Was ist angesagt?

Wprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache HadoopWprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache HadoopSages
 
Apache Commons - Don\'t re-invent the wheel
Apache Commons - Don\'t re-invent the wheelApache Commons - Don\'t re-invent the wheel
Apache Commons - Don\'t re-invent the wheeltcurdt
 
The Groovy Puzzlers – The Complete 01 and 02 Seasons
The Groovy Puzzlers – The Complete 01 and 02 SeasonsThe Groovy Puzzlers – The Complete 01 and 02 Seasons
The Groovy Puzzlers – The Complete 01 and 02 SeasonsBaruch Sadogursky
 
Codepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash course
Codepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash courseCodepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash course
Codepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash courseSages
 
The Ring programming language version 1.5.1 book - Part 44 of 180
The Ring programming language version 1.5.1 book - Part 44 of 180The Ring programming language version 1.5.1 book - Part 44 of 180
The Ring programming language version 1.5.1 book - Part 44 of 180Mahmoud Samir Fayed
 
The Ring programming language version 1.8 book - Part 30 of 202
The Ring programming language version 1.8 book - Part 30 of 202The Ring programming language version 1.8 book - Part 30 of 202
The Ring programming language version 1.8 book - Part 30 of 202Mahmoud Samir Fayed
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistAnton Arhipov
 
Java practice programs for beginners
Java practice programs for beginnersJava practice programs for beginners
Java practice programs for beginnersishan0019
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard LibraryNelson Glauber Leal
 
Advanced Java Practical File
Advanced Java Practical FileAdvanced Java Practical File
Advanced Java Practical FileSoumya Behera
 
Google Guava for cleaner code
Google Guava for cleaner codeGoogle Guava for cleaner code
Google Guava for cleaner codeMite Mitreski
 
Java 8 Stream API. A different way to process collections.
Java 8 Stream API. A different way to process collections.Java 8 Stream API. A different way to process collections.
Java 8 Stream API. A different way to process collections.David Gómez García
 
Programming with Python and PostgreSQL
Programming with Python and PostgreSQLProgramming with Python and PostgreSQL
Programming with Python and PostgreSQLPeter Eisentraut
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 SpringKiyotaka Oku
 
The Ring programming language version 1.5.4 book - Part 26 of 185
The Ring programming language version 1.5.4 book - Part 26 of 185The Ring programming language version 1.5.4 book - Part 26 of 185
The Ring programming language version 1.5.4 book - Part 26 of 185Mahmoud Samir Fayed
 
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...julien.ponge
 
The Ring programming language version 1.5.3 book - Part 25 of 184
The Ring programming language version 1.5.3 book - Part 25 of 184The Ring programming language version 1.5.3 book - Part 25 of 184
The Ring programming language version 1.5.3 book - Part 25 of 184Mahmoud Samir Fayed
 

Was ist angesagt? (20)

Wprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache HadoopWprowadzenie do technologi Big Data i Apache Hadoop
Wprowadzenie do technologi Big Data i Apache Hadoop
 
Google Guava
Google GuavaGoogle Guava
Google Guava
 
Apache Commons - Don\'t re-invent the wheel
Apache Commons - Don\'t re-invent the wheelApache Commons - Don\'t re-invent the wheel
Apache Commons - Don\'t re-invent the wheel
 
The Groovy Puzzlers – The Complete 01 and 02 Seasons
The Groovy Puzzlers – The Complete 01 and 02 SeasonsThe Groovy Puzzlers – The Complete 01 and 02 Seasons
The Groovy Puzzlers – The Complete 01 and 02 Seasons
 
Codepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash course
Codepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash courseCodepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash course
Codepot - Pig i Hive: szybkie wprowadzenie / Pig and Hive crash course
 
The Ring programming language version 1.5.1 book - Part 44 of 180
The Ring programming language version 1.5.1 book - Part 44 of 180The Ring programming language version 1.5.1 book - Part 44 of 180
The Ring programming language version 1.5.1 book - Part 44 of 180
 
The Ring programming language version 1.8 book - Part 30 of 202
The Ring programming language version 1.8 book - Part 30 of 202The Ring programming language version 1.8 book - Part 30 of 202
The Ring programming language version 1.8 book - Part 30 of 202
 
What is new in Java 8
What is new in Java 8What is new in Java 8
What is new in Java 8
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with Javassist
 
Java practice programs for beginners
Java practice programs for beginnersJava practice programs for beginners
Java practice programs for beginners
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
 
Advanced Java Practical File
Advanced Java Practical FileAdvanced Java Practical File
Advanced Java Practical File
 
Google Guava for cleaner code
Google Guava for cleaner codeGoogle Guava for cleaner code
Google Guava for cleaner code
 
Java 8 Stream API. A different way to process collections.
Java 8 Stream API. A different way to process collections.Java 8 Stream API. A different way to process collections.
Java 8 Stream API. A different way to process collections.
 
Programming with Python and PostgreSQL
Programming with Python and PostgreSQLProgramming with Python and PostgreSQL
Programming with Python and PostgreSQL
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
 
The Ring programming language version 1.5.4 book - Part 26 of 185
The Ring programming language version 1.5.4 book - Part 26 of 185The Ring programming language version 1.5.4 book - Part 26 of 185
The Ring programming language version 1.5.4 book - Part 26 of 185
 
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
Java 7 Launch Event at LyonJUG, Lyon France. Fork / Join framework and Projec...
 
The Ring programming language version 1.5.3 book - Part 25 of 184
The Ring programming language version 1.5.3 book - Part 25 of 184The Ring programming language version 1.5.3 book - Part 25 of 184
The Ring programming language version 1.5.3 book - Part 25 of 184
 
Java 7 LavaJUG
Java 7 LavaJUGJava 7 LavaJUG
Java 7 LavaJUG
 

Ähnlich wie Joker 2015 - Валеев Тагир - Что же мы измеряем?

Locks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael BarkerLocks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael BarkerJAX London
 
Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!Michael Barker
 
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...Yauheni Akhotnikau
 
Debugging Ruby
Debugging RubyDebugging Ruby
Debugging RubyAman Gupta
 
Benchmarking Perl (Chicago UniForum 2006)
Benchmarking Perl (Chicago UniForum 2006)Benchmarking Perl (Chicago UniForum 2006)
Benchmarking Perl (Chicago UniForum 2006)brian d foy
 
Actor Concurrency
Actor ConcurrencyActor Concurrency
Actor ConcurrencyAlex Miller
 
Performance tweaks and tools for Linux (Joe Damato)
Performance tweaks and tools for Linux (Joe Damato)Performance tweaks and tools for Linux (Joe Damato)
Performance tweaks and tools for Linux (Joe Damato)Ontico
 
An eternal question of timing
An eternal question of timingAn eternal question of timing
An eternal question of timingPVS-Studio
 
Benchmarking and PHPBench
Benchmarking and PHPBenchBenchmarking and PHPBench
Benchmarking and PHPBenchdantleech
 
Alexander Reelsen - Seccomp for Developers
Alexander Reelsen - Seccomp for DevelopersAlexander Reelsen - Seccomp for Developers
Alexander Reelsen - Seccomp for DevelopersDevDay Dresden
 
Debugging Ruby Systems
Debugging Ruby SystemsDebugging Ruby Systems
Debugging Ruby SystemsEngine Yard
 
Lecture18-19 (1).ppt
Lecture18-19 (1).pptLecture18-19 (1).ppt
Lecture18-19 (1).pptssuserf67e3a
 
Verilog Lecture3 hust 2014
Verilog Lecture3 hust 2014Verilog Lecture3 hust 2014
Verilog Lecture3 hust 2014Béo Tú
 
D Trace Support In My Sql Guide To Solving Reallife Performance Problems
D Trace Support In My Sql Guide To Solving Reallife Performance ProblemsD Trace Support In My Sql Guide To Solving Reallife Performance Problems
D Trace Support In My Sql Guide To Solving Reallife Performance ProblemsMySQLConference
 
DIY Java Profiler
DIY Java ProfilerDIY Java Profiler
DIY Java Profileraragozin
 
Where the wild things are - Benchmarking and Micro-Optimisations
Where the wild things are - Benchmarking and Micro-OptimisationsWhere the wild things are - Benchmarking and Micro-Optimisations
Where the wild things are - Benchmarking and Micro-OptimisationsMatt Warren
 

Ähnlich wie Joker 2015 - Валеев Тагир - Что же мы измеряем? (20)

Writing Faster Python 3
Writing Faster Python 3Writing Faster Python 3
Writing Faster Python 3
 
Locks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael BarkerLocks? We Don't Need No Stinkin' Locks - Michael Barker
Locks? We Don't Need No Stinkin' Locks - Michael Barker
 
Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!Lock? We don't need no stinkin' locks!
Lock? We don't need no stinkin' locks!
 
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
Shrimp: A Rather Practical Example Of Application Development With RESTinio a...
 
Debugging Ruby
Debugging RubyDebugging Ruby
Debugging Ruby
 
Benchmarking Perl (Chicago UniForum 2006)
Benchmarking Perl (Chicago UniForum 2006)Benchmarking Perl (Chicago UniForum 2006)
Benchmarking Perl (Chicago UniForum 2006)
 
Ping to Pong
Ping to PongPing to Pong
Ping to Pong
 
Cramp websockets
Cramp websocketsCramp websockets
Cramp websockets
 
Code Tuning
Code TuningCode Tuning
Code Tuning
 
Actor Concurrency
Actor ConcurrencyActor Concurrency
Actor Concurrency
 
Performance tweaks and tools for Linux (Joe Damato)
Performance tweaks and tools for Linux (Joe Damato)Performance tweaks and tools for Linux (Joe Damato)
Performance tweaks and tools for Linux (Joe Damato)
 
An eternal question of timing
An eternal question of timingAn eternal question of timing
An eternal question of timing
 
Benchmarking and PHPBench
Benchmarking and PHPBenchBenchmarking and PHPBench
Benchmarking and PHPBench
 
Alexander Reelsen - Seccomp for Developers
Alexander Reelsen - Seccomp for DevelopersAlexander Reelsen - Seccomp for Developers
Alexander Reelsen - Seccomp for Developers
 
Debugging Ruby Systems
Debugging Ruby SystemsDebugging Ruby Systems
Debugging Ruby Systems
 
Lecture18-19 (1).ppt
Lecture18-19 (1).pptLecture18-19 (1).ppt
Lecture18-19 (1).ppt
 
Verilog Lecture3 hust 2014
Verilog Lecture3 hust 2014Verilog Lecture3 hust 2014
Verilog Lecture3 hust 2014
 
D Trace Support In My Sql Guide To Solving Reallife Performance Problems
D Trace Support In My Sql Guide To Solving Reallife Performance ProblemsD Trace Support In My Sql Guide To Solving Reallife Performance Problems
D Trace Support In My Sql Guide To Solving Reallife Performance Problems
 
DIY Java Profiler
DIY Java ProfilerDIY Java Profiler
DIY Java Profiler
 
Where the wild things are - Benchmarking and Micro-Optimisations
Where the wild things are - Benchmarking and Micro-OptimisationsWhere the wild things are - Benchmarking and Micro-Optimisations
Where the wild things are - Benchmarking and Micro-Optimisations
 

Kürzlich hochgeladen

OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...
OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...
OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...NETWAYS
 
Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...
Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...
Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...Kayode Fayemi
 
CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...
CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...
CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...henrik385807
 
Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...
Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...
Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...Hasting Chen
 
CTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdf
CTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdfCTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdf
CTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdfhenrik385807
 
Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...
Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...
Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...Salam Al-Karadaghi
 
George Lever - eCommerce Day Chile 2024
George Lever -  eCommerce Day Chile 2024George Lever -  eCommerce Day Chile 2024
George Lever - eCommerce Day Chile 2024eCommerce Institute
 
Presentation on Engagement in Book Clubs
Presentation on Engagement in Book ClubsPresentation on Engagement in Book Clubs
Presentation on Engagement in Book Clubssamaasim06
 
Introduction to Prompt Engineering (Focusing on ChatGPT)
Introduction to Prompt Engineering (Focusing on ChatGPT)Introduction to Prompt Engineering (Focusing on ChatGPT)
Introduction to Prompt Engineering (Focusing on ChatGPT)Chameera Dedduwage
 
Thirunelveli call girls Tamil escorts 7877702510
Thirunelveli call girls Tamil escorts 7877702510Thirunelveli call girls Tamil escorts 7877702510
Thirunelveli call girls Tamil escorts 7877702510Vipesco
 
SaaStr Workshop Wednesday w: Jason Lemkin, SaaStr
SaaStr Workshop Wednesday w: Jason Lemkin, SaaStrSaaStr Workshop Wednesday w: Jason Lemkin, SaaStr
SaaStr Workshop Wednesday w: Jason Lemkin, SaaStrsaastr
 
VVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara Services
VVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara ServicesVVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara Services
VVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara ServicesPooja Nehwal
 
Microsoft Copilot AI for Everyone - created by AI
Microsoft Copilot AI for Everyone - created by AIMicrosoft Copilot AI for Everyone - created by AI
Microsoft Copilot AI for Everyone - created by AITatiana Gurgel
 
WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )
WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )
WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )Pooja Nehwal
 
BDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort ServiceBDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort ServiceDelhi Call girls
 
ANCHORING SCRIPT FOR A CULTURAL EVENT.docx
ANCHORING SCRIPT FOR A CULTURAL EVENT.docxANCHORING SCRIPT FOR A CULTURAL EVENT.docx
ANCHORING SCRIPT FOR A CULTURAL EVENT.docxNikitaBankoti2
 
Russian Call Girls in Kolkata Vaishnavi 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Vaishnavi 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Vaishnavi 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Vaishnavi 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
Mathematics of Finance Presentation.pptx
Mathematics of Finance Presentation.pptxMathematics of Finance Presentation.pptx
Mathematics of Finance Presentation.pptxMoumonDas2
 
Call Girl Number in Khar Mumbai📲 9892124323 💞 Full Night Enjoy
Call Girl Number in Khar Mumbai📲 9892124323 💞 Full Night EnjoyCall Girl Number in Khar Mumbai📲 9892124323 💞 Full Night Enjoy
Call Girl Number in Khar Mumbai📲 9892124323 💞 Full Night EnjoyPooja Nehwal
 
No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...
No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...
No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...Sheetaleventcompany
 

Kürzlich hochgeladen (20)

OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...
OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...
OSCamp Kubernetes 2024 | A Tester's Guide to CI_CD as an Automated Quality Co...
 
Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...
Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...
Governance and Nation-Building in Nigeria: Some Reflections on Options for Po...
 
CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...
CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...
CTAC 2024 Valencia - Sven Zoelle - Most Crucial Invest to Digitalisation_slid...
 
Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...
Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...
Re-membering the Bard: Revisiting The Compleat Wrks of Wllm Shkspr (Abridged)...
 
CTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdf
CTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdfCTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdf
CTAC 2024 Valencia - Henrik Hanke - Reduce to the max - slideshare.pdf
 
Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...
Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...
Exploring protein-protein interactions by Weak Affinity Chromatography (WAC) ...
 
George Lever - eCommerce Day Chile 2024
George Lever -  eCommerce Day Chile 2024George Lever -  eCommerce Day Chile 2024
George Lever - eCommerce Day Chile 2024
 
Presentation on Engagement in Book Clubs
Presentation on Engagement in Book ClubsPresentation on Engagement in Book Clubs
Presentation on Engagement in Book Clubs
 
Introduction to Prompt Engineering (Focusing on ChatGPT)
Introduction to Prompt Engineering (Focusing on ChatGPT)Introduction to Prompt Engineering (Focusing on ChatGPT)
Introduction to Prompt Engineering (Focusing on ChatGPT)
 
Thirunelveli call girls Tamil escorts 7877702510
Thirunelveli call girls Tamil escorts 7877702510Thirunelveli call girls Tamil escorts 7877702510
Thirunelveli call girls Tamil escorts 7877702510
 
SaaStr Workshop Wednesday w: Jason Lemkin, SaaStr
SaaStr Workshop Wednesday w: Jason Lemkin, SaaStrSaaStr Workshop Wednesday w: Jason Lemkin, SaaStr
SaaStr Workshop Wednesday w: Jason Lemkin, SaaStr
 
VVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara Services
VVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara ServicesVVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara Services
VVIP Call Girls Nalasopara : 9892124323, Call Girls in Nalasopara Services
 
Microsoft Copilot AI for Everyone - created by AI
Microsoft Copilot AI for Everyone - created by AIMicrosoft Copilot AI for Everyone - created by AI
Microsoft Copilot AI for Everyone - created by AI
 
WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )
WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )
WhatsApp 📞 9892124323 ✅Call Girls In Juhu ( Mumbai )
 
BDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort ServiceBDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort Service
BDSM⚡Call Girls in Sector 93 Noida Escorts >༒8448380779 Escort Service
 
ANCHORING SCRIPT FOR A CULTURAL EVENT.docx
ANCHORING SCRIPT FOR A CULTURAL EVENT.docxANCHORING SCRIPT FOR A CULTURAL EVENT.docx
ANCHORING SCRIPT FOR A CULTURAL EVENT.docx
 
Russian Call Girls in Kolkata Vaishnavi 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Vaishnavi 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Vaishnavi 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Vaishnavi 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
Mathematics of Finance Presentation.pptx
Mathematics of Finance Presentation.pptxMathematics of Finance Presentation.pptx
Mathematics of Finance Presentation.pptx
 
Call Girl Number in Khar Mumbai📲 9892124323 💞 Full Night Enjoy
Call Girl Number in Khar Mumbai📲 9892124323 💞 Full Night EnjoyCall Girl Number in Khar Mumbai📲 9892124323 💞 Full Night Enjoy
Call Girl Number in Khar Mumbai📲 9892124323 💞 Full Night Enjoy
 
No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...
No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...
No Advance 8868886958 Chandigarh Call Girls , Indian Call Girls For Full Nigh...
 

Joker 2015 - Валеев Тагир - Что же мы измеряем?

  • 1. Что же мы измеряем? Валеев Тагир Институт систем информатики СО РАН 1
  • 4. Насколько Stream API медленнее? static long sumTwice(int max) { long sum = 0; for(int i=1; i<=max; i++) sum+=i*2; return sum; } 4 static long sumTwiceStream(int max) { return IntStream.rangeClosed(1, max) .mapToLong(x -> x*2).sum(); }
  • 5. Спрашивает StackOverflow 5 http://stackoverflow.com/q/31761271/4856258 (удалено автором) Predicate<Integer> в Java 8 быстрее, чем IntPredicate В Java 8 предлагается использовать IntPredicate вместо Predicate<Integer>и аналогично для других примитивных типов, так как таким образом можно избавиться от накладных расходов на автобоксинг, но когда я запускаю нижеследующий код, я получаю совершенно противоположный результат: на моей системе IntPredicate в 30-50 раз медленее, чем Predicate. In Java 8 Predicate<Integer> is faster than IntPredicate In Java 8 it is suggested that we should use IntPredicate rather than Predicate<Integer> and same for other premitive types as former one reduces the overhead related to autoboxing but when i run the following code. I get results shockingly opposite as IntPredicate is 30-50 times slower than Predicate on my system.
  • 6. Спрашивает StackOverflow 6 Long start = System.currentTimeMillis(); IntPredicate evenNumPredicate = (int i) -> i % 2 == 0; evenNumPredicate.test(1000); System.out.println(System.currentTimeMillis()-start); start = System.currentTimeMillis(); Predicate<Integer> evenNumPredicate1 = (Integer i) -> i % 2 == 0; evenNumPredicate1.test(1000); System.out.println(System.currentTimeMillis()-start);
  • 7. Реакция сообщества Это потрясающе плохой бенчмарк. В него можно внести несколько кардинальных улучшений, и это всё равно будет плохой бенчмарк. – Marko Topolnik This benchmark is shockingly bad. It could be improved in several significant ways and still be a bad benchmark. 7
  • 8. Наивняк public static void main(String[] args) { long startSimple = System.nanoTime(); long resultSimple = sumTwice(10_000_000); long endSimple = System.nanoTime(); System.out.printf("Simple: %d; time=%8.3fms%n", resultSimple, (endSimple-startSimple)/1_000_000.0); long startStream = System.nanoTime(); long resultStream = sumTwiceStream(10_000_000); long endStream = System.nanoTime(); System.out.printf("Stream: %d; time=%8.3fms%n", resultStream, (endStream-startStream)/1_000_000.0); } 8
  • 9. Результаты наивняка 9 Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms -Xint Simple: 100000010000000; time= 136.347ms Stream: 100000010000000; time=1647.296ms -XX:-UseOnStackReplacement Simple: 100000010000000; time= 150.321ms Stream: 100000010000000; time= 374.367ms -XX:-UseOnStackReplacement -XX:-UseLoopCounter Simple: 100000010000000; time= 136.584ms Stream: 100000010000000; time= 364.105ms
  • 10. Насколько Stream API медленнее? static long sumTwice(int max) { long sum = 0; for(int i=1; i<=max; i++) sum+=i*2; return sum; } 10 static long sumTwiceStream(int max) { return IntStream.rangeClosed(1, max) .mapToLong(x -> x*2).sum(); }
  • 12. Результаты наивняка 12 Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms -Xint Simple: 100000010000000; time= 136.347ms Stream: 100000010000000; time=1647.296ms -XX:-UseOnStackReplacement Simple: 100000010000000; time= 150.321ms Stream: 100000010000000; time= 374.367ms -XX:-UseOnStackReplacement -XX:-UseLoopCounter Simple: 100000010000000; time= 136.584ms Stream: 100000010000000; time= 364.105ms
  • 14. Результаты наивняка 14 Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms -Xint Simple: 100000010000000; time= 136.347ms Stream: 100000010000000; time=1647.296ms -XX:-UseOnStackReplacement Simple: 100000010000000; time= 150.321ms Stream: 100000010000000; time= 374.367ms -XX:-UseOnStackReplacement -XX:-UseLoopCounter Simple: 100000010000000; time= 136.584ms Stream: 100000010000000; time= 364.105ms
  • 15. Результаты наивняка Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms -Xint Simple: 100000010000000; time= 136.347ms Stream: 100000010000000; time=1647.296ms -XX:-UseOnStackReplacement Simple: 100000010000000; time= 150.321ms Stream: 100000010000000; time= 374.367ms -XX:-UseOnStackReplacement -XX:-UseLoopCounter Simple: 100000010000000; time= 136.584ms Stream: 100000010000000; time= 364.105ms 15
  • 16. Stream-операции 16 return IntStream .rangeClosed(1, max) // создание Stream .mapToLong(x -> x*2) // промежуточная операция .sum(); // конечная операция
  • 17. Иерархия вызовов Stream LongPipeline::sum LongPipeline::reduce AbstractPipeline::evaluate ReduceOp::evaluateSequential AbstractPipeline::wrapAndCopyInto AbstractPipeline::copyInto Spliterator.OfInt::forEachRemaining RangeIntSpliterator::forEachRemaining (цикл) IntPipeline$5$1::accept (mapToLong) сгенерированный λ-класс λ-функция x -> x*2 ReducingSink::accept (reduce) Long::sum сгенерированный λ-класс 17
  • 18. Иерархия вызовов Stream LongPipeline::sum LongPipeline::reduce AbstractPipeline::evaluate ReduceOp::evaluateSequential AbstractPipeline::wrapAndCopyInto AbstractPipeline::copyInto Spliterator.OfInt::forEachRemaining RangeIntSpliterator::forEachRemaining (цикл) IntPipeline$5$1::accept (mapToLong) сгенерированный λ-класс λ-функция x -> x*2 ReducingSink::accept (reduce) Long::sum сгенерированный λ-класс 18 МАГИЯ
  • 19. Иерархия вызовов Stream 19 LongPipeline::sum LongPipeline::reduce AbstractPipeline::evaluate ReduceOp::evaluateSequential AbstractPipeline::wrapAndCopyInto AbstractPipeline::copyInto Spliterator.OfInt::forEachRemaining RangeIntSpliterator::forEachRemaining (цикл) IntPipeline$5$1::accept (mapToLong) сгенерированный λ-класс λ-функция x -> x*2 ReducingSink::accept (reduce) Long::sum сгенерированный λ-класс МАГИЯ
  • 20. Результаты наивняка 20 Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms -Xint Simple: 100000010000000; time= 136.347ms Stream: 100000010000000; time=1647.296ms -XX:-UseOnStackReplacement Simple: 100000010000000; time= 150.321ms Stream: 100000010000000; time= 374.367ms -XX:-UseOnStackReplacement -XX:-UseLoopCounter Simple: 100000010000000; time= 136.584ms Stream: 100000010000000; time= 364.105ms
  • 21. JMH • Инструкция и примеры здесь: http://openjdk.java.net/projects/code-tools/jmh/ • $ mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=org.sample -DartifactId=test -Dversion=1.0 • $ mvn clean install • pom.xml: <javac.target>1.6</javac.target> 21
  • 22. JMH benchmark public class MyBenchmark { @Benchmark public long stream() { return sumTwiceStream(10_000_000); } @Benchmark public long simple() { return sumTwice(10_000_000); } ... } 22
  • 23. JMH benchmark @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Fork(5) @State(Scope.Benchmark) public class MyBenchmark { ... } [jmhtest/target]$ java –jar benchmark.jar >out.txt 23
  • 24. JMH benchmark – результаты # JMH 1.11.1 (released 7 days ago) # VM version: JDK 1.8.0_60, VM 25.60-b23 ... Benchmark Mode Cnt Score Error Units MyBenchmark.simple avgt 50 4.535 ± 0.009 ms/op MyBenchmark.stream avgt 50 4.123 ± 0.009 ms/op ____________________________________________________________ Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms 24
  • 25. JMH benchmark – результаты # JMH 1.11.1 (released 7 days ago) # VM version: JDK 1.8.0_60, VM 25.60-b23 ... Benchmark Mode Cnt Score Error Units MyBenchmark.simple avgt 50 4.535 ± 0.009 ms/op MyBenchmark.stream avgt 50 4.123 ± 0.009 ms/op ____________________________________________________________ Simple: 100000010000000; time= 8.286ms Stream: 100000010000000; time= 57.774ms 25
  • 26. JMH – параметризуем @Param({"100000", "1000000", "10000000"}) private int n; @Benchmark public long stream() { return sumTwiceStream(n); } @Benchmark public long simple() { return sumTwice(n); } 26
  • 27. JMH с параметром – результаты Benchmark (n) Score Error Units MyBenchmark.simple 100000 0.048 ± 0.001 ms/op MyBenchmark.simple 1000000 0.442 ± 0.001 ms/op MyBenchmark.simple 10000000 4.608 ± 0.012 ms/op MyBenchmark.stream 100000 0.567 ± 0.002 ms/op MyBenchmark.stream 1000000 5.776 ± 0.025 ms/op MyBenchmark.stream 10000000 4.079 ± 0.012 ms/op 27
  • 28. n = 1_000_000 # Warmup Iteration 1: 0.446 ms/op # Warmup Iteration 2: 0.414 ms/op # Warmup Iteration 3: 1.144 ms/op # Warmup Iteration 4: 5.729 ms/op # Warmup Iteration 5: 5.792 ms/op Iteration 1: 5.821 ms/op Iteration 2: 5.751 ms/op Iteration 3: 5.733 ms/op ... Iteration 9: 5.787 ms/op Iteration 10: 5.829 ms/op 28
  • 29. n = 10_000_000 # Warmup Iteration 1: 4.342 ms/op # Warmup Iteration 2: 4.132 ms/op # Warmup Iteration 3: 4.131 ms/op # Warmup Iteration 4: 4.113 ms/op # Warmup Iteration 5: 4.083 ms/op Iteration 1: 4.134 ms/op Iteration 2: 4.121 ms/op Iteration 3: 4.127 ms/op ... Iteration 9: 4.099 ms/op Iteration 10: 4.124 ms/op 29
  • 30. n = 10_000_000 [jmhtest/target]$ java –jar benchmark.jar –i 20 >out.txt # Warmup Iteration 1: 4.360 ms/op # Warmup Iteration 2: 4.135 ms/op ... # Warmup Iteration 5: 4.084 ms/op Iteration 1: 4.131 ms/op Iteration 2: 4.111 ms/op ... Iteration 16: 4.114 ms/op Iteration 17: 4.114 ms/op Iteration 18: 19.181 ms/op Iteration 19: 57.917 ms/op Iteration 20: 57.899 ms/op 30
  • 31. Наивняк с циклом public static void main(String[] args) { for(int i=0; i<6000; i++) { long start = System.nanoTime(); long result = sumTwiceStream(10_000_000); long end = System.nanoTime(); System.out.printf("#%d: %d; time=%8.3fms%n", i, result, (end-start)/1_000_000.0); } } 31
  • 32. Наивняк с циклом – результат #0: 100000010000000; time= 63.484ms #1: 100000010000000; time= 10.328ms #2: 100000010000000; time= 3.931ms #3: 100000010000000; time= 4.051ms … #5630: 100000010000000; time= 3.938ms #5631: 100000010000000; time= 4.154ms #5632: 100000010000000; time= 4.150ms #5633: 100000010000000; time= 3.955ms #5634: 100000010000000; time= 4.011ms #5635: 100000010000000; time= 58.184ms #5636: 100000010000000; time= 58.058ms #5637: 100000010000000; time= 57.024ms 32
  • 33. -XX:+PrintCompilation 77 1 3 java.lang.String::equals (81 bytes) 78 2 3 java.lang.String::hashCode (55 bytes) ... 80 13 n 0 java.lang.System::arraycopy (native) (static) ... 81 16 s 3 java.lang.StringBuffer::append (13 bytes) ... 106 68 ! 3 java.lang.ref.ReferenceQueue::poll (28 bytes) ... 141 221 % 4 ...$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes) 144 219 % 3 ...$RangeIntSpliterator::forEachRemaining @ -2 (65 bytes) made not entrant 33
  • 34. -XX:+PrintCompilation: читаем лог 148 221 % 4 ...::forEachRemaining @ 34 (65 bytes) tstamp compile_id attrs comp_level name [@ osr_pos] (size) [status] • tstamp – время в миллисекундах с начала выполнения • compile_id – номер задачи на компиляцию в очереди • attrs – атрибуты • comp_level – номер уровня tier-компиляции • name – имя класса и метода • osr_pos – позиция в байткоде, на которой выполняется OSR • size – размер байткода метода в байтах (или “native”) • status – дополнительная информация о событии 34
  • 35. -XX:+PrintCompilation: атрибуты 148 221 % 4 ...::forEachRemaining @ 34 (65 bytes) tstamp compile_id attrs comp_level name [@ osr_pos] (size) [status] • n – обёртка для native-метода (по факту не компиляция) • % – on-stack replacement • s – метод объявлен synchronized • ! – есть обработчик исключений • b – компиляция блокирует выполнение 35
  • 36. -XX:+PrintCompilation: comp_level 148 221 % 4 ...::forEachRemaining @ 34 (65 bytes) tstamp compile_id attrs comp_level name [@ osr_pos] (size) [status] • 0 – none (интерпретатор / native-wrapper) • 1 – simple (C1-компилятор) • 2 – limited_profile (С1-компилятор с подсчётом вызовов и итераций циклов) • 3 – full_profile (уровень 2 плюс профилирование типов) • 4 – C2-компилятор 36 С1 С2Интерпретатор С2
  • 37. -XX:+PrintCompilation: status 141 221 % 4 ...::forEachRemaining @ 34 (65 bytes) 144 219 % 3 ...::forEachRemaining @ -2 (65 bytes) made not entrant 37 Вход воспрещён
  • 38. -XX:+PrintCompilation: status • made zombie – метод не используется, готов к удалению 38
  • 39. -XX:+TraceNMethodInstalls nmethod — JIT-компилированный метод (OSR или обычный) -XX:+UnlockDiagnosticVMOptions 59 1 3 java.lang.String::hashCode (55 bytes) Installing method (3) java.lang.String.hashCode()I 59 2 3 java.lang.String::equals (81 bytes) Installing method (3) java.lang.String.equals(Ljava/lang/Object;)Z 60 3 3 java.lang.String::indexOf (70 bytes) 60 6 n 0 java.lang.System::arraycopy (native) (static) Installing method (3) java.lang.String.indexOf(II)I 61 4 3 java.lang.Object::<init> (1 bytes) Installing method (3) java.lang.Object.<init>()V 39
  • 40. -XX:+PrintCompilation -XX:+TraceNMethodInstalls #5630: 100000010000000; time= 3.896ms 22939 567 4 Test::sumTwiceStream (21 bytes) #5631: 100000010000000; time= 4.327ms #5632: 100000010000000; time= 4.029ms #5633: 100000010000000; time= 4.170ms 22954 474 3 Test::sumTwiceStream (21 bytes) made not entrant Installing method (4) Test.sumTwiceStream(I)J #5634: 100000010000000; time= 4.072ms 22956 568 4 j.u.s.LongPipeline$$...::applyAsLong (6 bytes) 22956 204 3 j.u.s.LongPipeline$$...::applyAsLong (6 bytes) made not entrant Installing method (4) j.u.s.LongPipeline$$Lambda$2/142257191.applyAsLong(JJ)J #5635: 100000010000000; time= 58.784ms 40
  • 42. Инлайнинг 42 static long sumTwice(int max) { long sum = 0; for(int i=1; i<=max; i++) sum+=mult(i); return sum; } static int mult(int x) { return x*2; } static long sumTwice(int max) { long sum = 0; for(int i=1; i<=max; i++) sum+=i*2; return sum; } -XX:+PrintInlining
  • 43. -XX:+PrintInlining j.u.s.Streams$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes) @ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot) -> TypeProfile (55084/55084 counts) = j/u/s/IntPipeline$5$1 @ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot) -> TypeProfile (24272/24272 counts) = Test$$Lambda$1 @ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inline (hot) @ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot) -> TypeProfile (24272/24272 counts) = j/u/s/ReduceOps$8ReducingSink @ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inline (hot) -> TypeProfile (7376/7376 counts) = j/u/s/LongPipeline$$Lambda$2 @ 2 java.lang.Long::sum (4 bytes) inline (hot) 43
  • 44. -XX:+PrintInlining j.u.s.Streams$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes) @ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot) -> TypeProfile (55084/55084 counts) = j/u/s/IntPipeline$5$1 @ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot) -> TypeProfile (24272/24272 counts) = Test$$Lambda$1 @ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inline (hot) @ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot) -> TypeProfile (24272/24272 counts) = j/u/s/ReduceOps$8ReducingSink @ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inline (hot) -> TypeProfile (7376/7376 counts) = j/u/s/LongPipeline$$Lambda$2 @ 2 java.lang.Long::sum (4 bytes) inline (hot) 44
  • 45. JITWatch – compile chain 45 https://github.com/AdoptOpenJDK/jitwatch x -> x*2 Long::sum
  • 46. Скомпилированный код (до 5600) LongPipeline::sum LongPipeline::reduce AbstractPipeline::evaluate ReduceOp::evaluateSequential AbstractPipeline::wrapAndCopyInto AbstractPipeline::copyInto Spliterator.OfInt::forEachRemaining RangeIntSpliterator::forEachRemaining (цикл) IntPipeline$5$1::accept (mapToLong) сгенерированный λ-класс λ-функция x -> x*2 ReducingSink::accept (reduce) Long::sum сгенерированный λ-класс sumTwiceStream C1 C1 C1 C1 C1 C1 C1 C2 46
  • 47. -XX:+PrintInlining @ 12 j.u.s.Streams$RangeIntSpliterator::forEachRemaining (65 bytes) inline (hot) @ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot) @ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot) -> TypeProfile (8593/8593 counts) = Test$$Lambda$1 @ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inlining too deep @ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot) -> TypeProfile (8593/8593 counts) = java/util/stream/ReduceOps$8ReducingSink @ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inlining too deep -> TypeProfile (11206/11206 counts) = java/util/stream/LongPipeline$$Lambda$2 47
  • 48. JITWatch 48 Class: Test Method: lambda$sumTwiceStream$0 JIT Compiled: Yes Inlined: No, inlining too deep Count: 7194 iicount: 5599 Bytes: 5 Prof factor: 1
  • 49. Скомпилированный код (после 5600) LongPipeline::sum LongPipeline::reduce AbstractPipeline::evaluate ReduceOp::evaluateSequential AbstractPipeline::wrapAndCopyInto AbstractPipeline::copyInto Spliterator.OfInt::forEachRemaining RangeIntSpliterator::forEachRemaining (цикл) IntPipeline$5$1::accept (mapToLong) сгенерированный λ-класс λ-функция x -> x*2 ReducingSink::accept (reduce) Long::sum сгенерированный λ-класс sumTwiceStream C2 C2 C249
  • 50. java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version ... uintx MaxGCMinorPauseMillis = 4294967295 {product} uintx MaxGCPauseMillis = 4294967295 {product} uintx MaxHeapFreeRatio = 100 {manageable} uintx MaxHeapSize := 1069547520 {product} intx MaxInlineLevel = 9 {product} intx MaxInlineSize = 35 {product} intx MaxJNILocalCapacity = 65536 {product} intx MaxJavaStackTraceDepth = 1024 {product} intx MaxJumpTableSize = 65000 {C2 product} ... 50
  • 51. JMH; -XX:MaxInlineLevel=13 Benchmark (n) Score Error Units MyBenchmark.simple 100000 0.047 ± 0.001 ms/op MyBenchmark.simple 1000000 0.420 ± 0.002 ms/op MyBenchmark.simple 10000000 4.445 ± 0.029 ms/op MyBenchmark.stream 100000 0.038 ± 0.001 ms/op MyBenchmark.stream 1000000 0.379 ± 0.001 ms/op MyBenchmark.stream 10000000 4.102 ± 0.014 ms/op 51
  • 52. n = 1_000_000; MaxInlineLevel = 13 # Warmup Iteration 1: 0.436 ms/op # Warmup Iteration 2: 0.408 ms/op # Warmup Iteration 3: 0.388 ms/op # Warmup Iteration 4: 0.376 ms/op # Warmup Iteration 5: 0.374 ms/op Iteration 1: 0.377 ms/op Iteration 2: 0.375 ms/op Iteration 3: 0.376 ms/op ... Iteration 9: 0.375 ms/op Iteration 10: 0.376 ms/op 52 0.446 ms/op 0.414 ms/op 1.144 ms/op 5.729 ms/op 5.792 ms/op 5.821 ms/op 5.751 ms/op 5.733 ms/op ... 5.787 ms/op 5.829 ms/op А было так:
  • 54. TypeProfile j.u.s.Streams$RangeIntSpliterator::forEachRemaining @ 34 (65 bytes) @ 44 j.u.s.IntPipeline$5$1::accept (23 bytes) inline (hot) -> TypeProfile (55084/55084 counts) = j/u/s/IntPipeline$5$1 @ 12 …$$Lambda$1/321001045::applyAsLong (5 bytes) inline (hot) -> TypeProfile (24272/24272 counts) = Test$$Lambda$1 @ 1 Test::lambda$sumTwiceStream$0 (5 bytes) inline (hot) @ 17 j.u.s.ReduceOps$8ReducingSink::accept (19 bytes) inline (hot) -> TypeProfile (24272/24272 counts) = j/u/s/ReduceOps$8ReducingSink @ 10 …$$Lambda$2/303563356::applyAsLong (6 bytes) inline (hot) -> TypeProfile (7376/7376 counts) = j/u/s/LongPipeline$$Lambda$2 @ 2 java.lang.Long::sum (4 bytes) inline (hot) 54
  • 55. Profile pollution @Param({"0", "1", "2", "3"}) private int pollute; @Setup public void setup() { switch(pollute) { case 3: for(int i=0; i<1000; i++) IntStream.range(0,100).mapToLong(x -> x*3).sum(); case 2: for(int i=0; i<1000; i++) IntStream.range(0,100).mapToLong(x -> x*4).sum(); case 1: for(int i=0; i<1000; i++) IntStream.range(0,100).mapToLong(x -> x*5).sum(); } } 55
  • 56. -XX:MaxInlineLevel=13 + pollution Benchmark (n) (pollute) Score Error Units MB.stream 100000 0 0.038 ± 0.001 ms/op MB.stream 100000 1 0.047 ± 0.001 ms/op MB.stream 100000 2 0.510 ± 0.003 ms/op MB.stream 100000 3 0.509 ± 0.003 ms/op MB.stream 1000000 0 0.371 ± 0.002 ms/op MB.stream 1000000 1 0.435 ± 0.021 ms/op MB.stream 1000000 2 5.385 ± 0.067 ms/op MB.stream 1000000 3 5.433 ± 0.042 ms/op MB.stream 10000000 0 4.029 ± 0.019 ms/op MB.stream 10000000 1 4.550 ± 0.129 ms/op MB.stream 10000000 2 53.658 ± 0.812 ms/op MB.stream 10000000 3 54.563 ± 0.207 ms/op 56
  • 57. MaxInlineLevel=13 + Type pollution LongPipeline::sum LongPipeline::reduce AbstractPipeline::evaluate ReduceOp::evaluateSequential AbstractPipeline::wrapAndCopyInto AbstractPipeline::copyInto Spliterator.OfInt::forEachRemaining RangeIntSpliterator::forEachRemaining (цикл) IntPipeline$5$1::accept (mapToLong) сгенерированный λ-класс λ-функция x -> x*2 ReducingSink::accept (reduce) Long::sum сгенерированный λ-класс sumTwiceStream C2 C2 57
  • 58. Баги в OpenJDK • https://bugs.openjdk.java.net/browse/JDK-8015416 – tier one should collect context-dependent split profiles • https://bugs.openjdk.java.net/browse/JDK-8015417 – profile pollution after call through invokestatic to shared code 58
  • 59. На самом деле public static long sumTwiceOpt(int max) { return max*(max+1L); } Benchmark (n) Score Error Units MyBenchmark.opt 100000 0.003 ± 0.001 us/op MyBenchmark.opt 1000000 0.003 ± 0.001 us/op MyBenchmark.opt 10000000 0.003 ± 0.001 us/op 59
  • 60. Но всё не зря! -Xint -XX:UseOnStackReplacement -XX:UseLoopCounter -XX:MaxInlineLevel -XX:Tier4InvocationThreshold -XX:Tier4BackEdgeThreshold -XX:UnlockDiagnosticVMOptions -XX:PrintCompilation -XX:TraceNMethodInstalls -XX:PrintInlining -XX:PrintFlagsFinal 60 Опции виртуальной машины Инструменты JMH JITWatch
  • 61. Дополнительная информация • Алексей Шипилёв «The Black Magic of (Java) Method Dispatch» – http://shipilev.net/blog/2015/black-magic-method-dispatch/ • Владимир Иванов «Динамическая JIT-компиляция в JVM» – http://www.youtube.com/watch?v=oYu3HuIYDhI • Алексей Шипилёв «Java Benchmarking: как два таймстампа прочитать!» – http://shipilev.net/blog/2014/nanotrusting-nanotime/ – https://www.youtube.com/watch?v=Vb3jyHl3FNk • Пол Сандоз отвечает на «Erratic performance of Arrays.stream().map().sum()» – http://stackoverflow.com/a/25851390/4856258 61
  • 62. Всем спасибо! 62 Пони отсюда: http://fire-seeker.deviantart.com/

Hinweis der Redaktion

  1. Здравствуйте! Меня зовут Валеев Тагир. Мой доклад – это своеобразная сказка, основанная на реальных событиях. Мы посмотрим, как бывает непросто разобраться в том, что работает быстрее или медленнее. Вас ждёт пара адских бенчмарков и немного всякой всячины про JIT-компиляцию в HotSpot (других JVM я не касаюсь). Все тесты будут проводиться на Oracle JDK 8u60.
  2. Крутые докладчики обязательно показывают слайд-дисклеймер, я решил, что я не хуже, и тоже сделал такой. Вот только я не работаю в Оракле, так что дисклеймер писали не профессиональные юристы, а я сам, поэтому текста мало. Воспринимайте мои слова критически, не делайте далеко идущих выводов и проверяйте всё, что увидите, сами.
  3. Последнее время я очень полюбил Java 8 Stream API. Настолько, что написал классную библиотеку, расширяющую возможности стримов. Взять можно на гитхабе или в мавене бесплатно, без регистрации и sms. Это была реклама. В общем, как я увидел стримы, я от них затащился и стал пихать куда ни попадя. И, конечно, захотелось разобраться с вопросом: а какова цена удобства? Насколько я проиграю по скорости по сравнению с традиционными решениями?
  4. Ну как проверить? Надо придумать какую-нибудь задачу и замерить скорость её выполнения. Я решил, что надо взять какой-нибудь несложный тест, тогда будет проще разобраться в происходящем. Вот, например: берём целые числа от 1 до n, умножаем каждое на 2 и считаем сумму. Вот слева у нас старый добрый традиционный цикл. Всё простенько, без изысков. Результат запишем в long, чтоб не переполниться. Как видите, никаких выделений памяти и вообще обращений к куче, вычислений минимум, поэтому посторонних эффектов быть не должно. Справа у нас реализация на Stream API. Модно, функционально, современно, фичасто. Какая будет работать быстрее? Ну, надо думать, традиционная. Вопрос, насколько. Ну мы не хотим, чтобы тест занимал наносекунды, там могут быть погрешности, поэтому давайте возьмём max побольше, чтобы процедура заняла заметное время. Скажем, 10 миллионов, хорошее число. Устроим мортал комбат. Как будем измерять?
  5. Я последнее время стал на StackOverflow частенько зависать. Вот там с завидной регулярностью вопросы появляются в духе приведённого на слайде. Можно не вчитываться, это всего лишь пример. Вопросы в духе: «а вот, я вот так измерил A, а оно оказалось быстрее Б, а мне говорили, должно быть наоборот, как же так?» Или, гораздо хуже, появляются подобные ответы с рекомендациями, используйте А, оно быстрее, я измерил!
  6. При этом измерение выглядит примерно так. Ну а что такого? Измерил время в начале, измерил в конце, сравнил, вывел на экран.
  7. Реакция на такие вопросы предсказуемо вот такая. Фейспалмы, минусики, голоса за закрытие, ехидные комментарии и так далее. Хотя справедливости ради замечу, иногда бывает, что потрясающе плохой бенчмарк всё равно обнаруживает какой-нибудь интересный эффект.
  8. Давайте и мы начнём ламерским методом, чтобы просто понять, плох ли он и почему. Взяли, замерили время в начале, запустили функцию, замерили время в конце, вывели разность. Всё же до currentTimeMillis опускаться не будем, возьмём nanoTime. Нормально? Нормально. Запустим.
  9. Вот типичный результат на типичной не сильно новой машине. Кстати, заметьте, что даже в этом наивняке мы сделали важную вещь: вывели не только время, но и результат. Во-первых, если результат не использовать, виртуальная машина в некоторых случаях может вообще обидеться и не вычислять его. А зачем? Всё равно никому не надо. Кто знает, как такая оптимизация называется? Правильно, dead code elimination. Во-вторых, если вы сравниваете скорость работы двух вариантов решения одной задачи, для начала стоит убедиться, что два варианта вообще выдают одинаковый результат. А то некоторые сравнивают правильный алгоритм с неправильным и потом хвастаются, что неправильный быстрее. Примерно как машинистка, которая печатает со скоростью 1000 знаков в минуту. Мы видим, что результат одинаковый, но время удручающее: стрим в семь раз медленнее, чем старый добрый цикл. Я на всякий случай запустил программу несколько раз, результаты более-менее стабильные. Семикратный провал в скорости – это всё, конец? Стримы безнадёжно медленные? Ну да, мы знаем, что измерили неправильно. Но давайте попробуем понять, а что мы измерили? Давайте сперва с простым случаем разберёмся. 8 с лишним миллисекунд – это что за время?
  10. Вы помните, в простом случае совершенно тривиальный метод, должно быть не сильно сложно. 8 миллисекунд – это что за время? И может ли оно стать быстрее при повторном выполнении кода?
  11. Ну да, я что-то слышал, что сперва JVM интерпретирует код, а компилирует уже потом, при повторном выполнении метода. Примерно понятно, что интерпретатор едет медленно, как лошадь, зато сел и поскакал. А компилируемый код — это вроде паровоза, который едет быстрее, но его вначале надо раскочегарить, дров накидать. Если б все методы сразу JIT-компилировались, пришлось бы ждать, пока компиляция завершится, поэтому чтобы не терять времени, мы начинаем скакать на лошади, а в параллельном потоке раскочегаривается паровоз. Как потом пересесть с лошади на паровоз? Обычно это происходит на станции, то есть при повторном вызове метода. Но у нас только один метод sumTwice, он вызывается один раз и другие методы не вызывает. Выходит, шанса пересесть в паровоз на следующей станции у нас нету. Значит, 8 миллисекунд — это время выполнения кода интерпретатором? Или нет?
  12. К чему гадать, если можно проверить? Запустим JVM в режиме интерпретатора с помощью опции –Xint. Вот здесь мы действительно измерили время выполнения интерпретатором. Ага, оказывается, что компилятор всё-таки был полезен в первом тесте: теперь оба теста стали гораздо медленнее, причём стрим проигрывает раз в 12. Тут мы, кстати, уже видим, что одна и та же Java-программа может выполняться существенно разное время в зависимости от опций JVM. Поэтому вопрос вроде «сколько времени будет выполняться эта программа» малоосмысленен без контекста. Как же компилятору удаётся помочь в первом тесте?
  13. Не все знают, но у виртуальной машины HotSpot есть интересная фича OnStackReplacement (или OSR) – замена на стеке. Когда интерпретируемый метод содержит много обратных переходов (back edges), то есть в нём есть длинный цикл, этот метод компилируется JIT-компилятором в особый вид, который позволяет перепрыгнуть из медленного интерпретатора в быстрый скомпилированный код на полном скаку. Прыжок обычно производится в начале очередной итерации цикла. Разумеется, при этом аккуратно переносятся все переменные и вообще контекст выполнения. Как проверить, действительно ли OSR помогает в нашем случае? Очень просто – попробуем его отключить.
  14. Для этого, разумеется, тоже есть опция JVM – -UseOnStackReplacement. И тут сразу неожиданный результат: с этим параметром простая версия работает существенно медленнее, чем вообще без компилятора: 150 миллисекунд против 136. На самом деле виртуальная машина HotSpot — чрезвычайно настраиваемая штука. OnStackReplacement срабатывает, когда достигается сколько-то обратных переходов, но их ещё же надо сосчитать. Это делает отдельная подсистема — loop counter. В режиме интерпретатора она выключена, потому что не нужна. А вот выключив только OnStackReplacement, мы loop counter не выключили, и он добавляет работы интерпретатору, собирая статистику, сколько было обратных переходов.
  15. Отдельной опцией –UseLoopCounter можно выключить и его. Теперь время выполнения простого теста сравнялось с интерпретатором, так что похоже действительно OSR сильно помогал. Зато заметьте, что стрим версия хотя без OSR работает медленнее, но всё же гораздо быстрее, чем без интерпретатора вообще. Теперь она всего в 2.5 раза проигрывает. Как вы думаете, почему? Как компилятор помогает стрим-версии при отсутствии OSR?
  16. Чтобы понять, почему так происходит, надо взглянуть на устройство Stream API. Если кто-то ещё не в курсе, стримы по своей природе ленивы. Вы сперва создаёте стрим, потом можете использовать промежуточные (или intermediate) операции, при этом вычислений не происходит, просто операции добавляются в конвейер. А в конце вы запускаете ровно одну конечную или терминальную операцию (в данном случае sum), которая и делает всю работу. Это как в армии: «взвод, в направлении отдельно стоящего дерева, направляющее первое отделение, бегом – марш!» Пока «марш» не сказали, все стоят и слушают, что делать, а потом бегут. «Стрим, целые числа от одного до макс, умножив каждое на два, сложить!»
  17. Посмотрим, что происходит внутри этой конечной операции sum. Кто ловил исключения в стримах, знает, какие там суровые стектрейсы. Тут вот нарисована основная последовательность вызовов, в которой производятся собственно вычисления. Там ещё немало побочных методов вызывается, я их не стал приводить, иначе слайд бы совсем растаращило. Здесь только то, что ведёт непосредственно к нашим вычислениям. Sum на самом деле вызывает reduce с функцией Long::sum. Reduce вызывает… на самом деле нам не очень важно, что она вызывает.
  18. Чтобы не углубляться, будем считать, что это работает так и в какой-то момент мы сваливаемся в RangeIntSpliterator, где и выполняется цикл на 10 миллионов элементов (цикл while, если быть точным). То есть всё вышестоящее пролетает довольно быстро, а вот тут начинается реальное вычисление. Далее вызывается реализация тела mapToLong, которая в свою очередь вызывает нашу лямбду для умножения и передаёт управление реализации reduce-операции, а она суммирует.
  19. То есть даже без OSR-механизма но со включенной компиляцией можно скомпилировать вот это всё, и с какой-то итерации цикла оно начинает работать быстрее.
  20. Таким образом, про самый первый результат вообще сложно сказать, что же мы измерили. Это частично интерпретированный, частично скомпилированный код в какой-то сложной пропорции. Во втором случае ещё более-менее ясно, что мы измеряли скорость работы интерпретатора. Цифры практически бесполезные, но по крайней мере понятно. И то здесь ещё надо учесть задержки на загрузку тонны вспомогательных классов из Stream API, генерации рантайм-представления из лямбда-выражений и так далее. Поэтому если повторить тест, то даже в режиме интерпретации второй и последующие результаты будут процентов на 10 быстрее. А в первом случае полное мясо, цифры, которые ничего не значат. Если вам надо один раз решить задачу, вас же не волнует, завершится она за 8 миллисекунд или за 60. Вы в любом случае моргнуть не успеете. А если задачу выполнять много раз, то цифры будут совсем другие.
  21. Так что прекращаем измерять напряжение языком и возьмём, наконец, вольтметр. Есть несколько вариантов, но мы не будем выделяться из толпы и остановимся на JMH. Про JMH рассказывала куча докладчиков, но по моему впечатлению, многие до сих пор боятся, что поставить и настроить его сложно. Спешу заверить, это как раз несложно. В инструкции на сайте сказано, что нужен мавен, но если так случилось, что вы им никогда не пользовались, это не страшно. По факту нужны две команды, остальное всё заработает волшебным образом само. Одна длинная, вот она. Запоминать не надо, можно скопипастить. Её набираете один раз, мавен пошуршит, и у вас будет готовая болванка проекта для бенчмаркинга с тестовым классом. И вторая короткая – для сборки. Ещё загляните в pom.xml, там есть строчка javac.target, для наших тестов надо поставить 1.8. Всё, остальное можно не трогать и не вникать. Конечно, любой Java-программист просто обязан изучить мавен, но конкретно для JMH это необязательно.
  22. Вот давайте возьмём этот автогенерированный MyBenchmark.java и напишем там такой тест. Внизу опущены реализации sumTwice и sumTwiceStream, точно такие же, как в наивной версии. Результат вычислений можно просто вернуть из метода и не беспокоиться, что JIT-компилятор удалит вычисления. JMH любой возвращённый результат поглощает в специальную чёрную дыру, чтобы сделать вид, что он нам на самом деле нужен.
  23. Сам класс надо аннотировать для настройки. По умолчанию JMH измеряет не среднее время, а обратную величину – throughput. Чтобы результаты были похожи на наивный замер, установим режим среднего времени и единицу измерения – миллисекунды. Будем 5 секунд гонять вычисления без замера для разогрева, чтобы всё уж точно скомпилировалось, загрузилось и т. д. Потом 10 раз по секунде будем делать замеры. JMH не один раз запустит наш метод, а столько раз, сколько влезет в секунду. Чтобы сделать замер ещё надёжнее, укажем Fork пять. Это означает, что весь тест мы повторим пять раз честно с перезапуском виртуальной машины и повторным разогревом. Таким образом у нас будет 50 измеряемых итераций. После сборки в каталоге target появится файл benchmark.jar. Надо его просто запустить, перенаправив вывод в файл, а потом изучать содержимое файла.
  24. Сперва будет детальный вывод по каждому из тестов, а потом общий результат. Время, которое нас интересует, в колонке Score. Внизу я скопировал для сравнения результаты наивной версии. Видно, что после разогрева традиционная реализация ускорилась почти вдвое. Но в стрим, похоже, воткнули реактивный двигатель. Он отработал в 14 раз быстрее. Любопытно, что стрим отработал даже быстрее простой реализации. Несильно быстрее, процентов на 10, но этот результат статистически значим: ошибка-то весьма маленькая. То есть вы понимаете? Стримы-то мало того что удобнее и прикольнее, они ещё и быстрее! Мы ж вольтметром измеряли, всё правильно сделано. Я даже на разных машинах проверял, по крайней мере на интел 64 бит архитектуре везде результат близкий.
  25. То есть всё здорово! Пони прыгают и радуются! Будем теперь стримы везде пихать? Программы только быстрее от этого станут.
  26. Впрочем ни один учёный не будет сообщать об открытии, сделав измерение только в одних условиях. Поэтому и мы не будем спешить. Замер был сделан только для 10 миллионов элементов. Давайте для разнообразия повторим его для 100 тысяч и одного миллиона. С помощью JMH это легко сделать. Заводим какое-нибудь поле в классе. Аннотируем его @Param, в скобках перечисляем возможные значения, только обязательно в строковом виде, чтобы эта аннотация работала для любых типов. И потом свободно используем этот параметр в тестах. JMH сам переберёт перечисленные значения для каждого бенчмарка и соберёт результат в красивую табличку.
  27. И мы имеем вот такие результаты. Традиционная реализация ведёт себя совершенно логично: в 10 раз больше параметр – в 10 раз больше время. А вот стрим над нами поиздевался. Смотрите: 100 тысяч элементов обрабатывается 560 микросекунд, миллион элементов – 5.7 миллисекунд, а 10 миллионов элементов – 4 миллисекунды, как и прежде! Это как вообще так? Миллион перебрать медленнее, чем 10 миллионов? На 10 миллионах стримы на коне, а если меньше, врубается черепаший режим, причём проигрыш по скорости уже даже не в 7 раз, а в 13! Погрешности небольшие, результат воспроизводится на разных машинах. Что происходит, есть идеи, куда копать? И что теперь, использовать стримы или не использовать? Использовать, только если число элементов больше 10 миллионов?
  28. Первым делом, конечно, стоит глянуть в лог замеров. Он не зря выводится. Вот как выглядит первый форк из пяти для стрим-реализации при n равном миллион. В остальных очень похожие цифры. Не замечаете ничего странного? Ага, первые две разогревочные итерации всё работало быстро: 440 микросекунд, 410 микросекунд – такой результат нас бы устроил. Потом на третьей что-то случилось, и всё, пошло-поехало.
  29. А вот для сравнения лог для 10 миллионов замеров. Тут как и ожидается, начинаем в 10 раз медленнее – 4.3 миллисекунды, 4.1 миллисекунда, но дальше почему-то не пошло и не поехало. Как думаете, почему? На самом деле просто не успело. Дело в том, что скорость падает после определённого количества вызовов метода sumTwiceStream. При n = миллион это количество достигается уже на третьей разогревочной итерации, а для 10 миллионов в итерацию влезает меньше вызовов.
  30. Как это проверить? Давайте добавим ещё 10 итераций. Для этого, кстати, необязательно исправлять аннотацию и перекомпилировать исходник: собранный benchmark.jar поддерживает кучу параметров командной строки, в том числе чтобы переопределить аннотации. Запустите с параметром –help, чтобы увидеть полный список. Нам нужен параметр –i 20. Запустим и увидим, что теперь и 10 миллионов сломали, на 18-й итерации скорость начала падать и дальше упала настолько же. Так что даже для 10 миллионов операций стрим-версия рано или поздно сдуется. Пони грустят. Всё, стримы не используем? Давайте прикинем, сколько вызовов sumTwiceStream проходит перед поломкой. У нас успешно отработало 5 разогревочных итераций и 17 основных – всего примерно 22 секунды, а потом произошла поломка. По результатам выше один тест в среднем занимает около 4 миллисекунд, делим 22 секунды на 4 миллисекунды, получим оценку, что что-то ломается где-то в районе итерации 5500.
  31. Чтобы посмотреть на этот эффект точнее, вспомним про старую добрую наивную версию. Простой вариант выкинем, он нам не нужен уже. Вместо этого добавим цикл и будем выводить номер итерации. Возможно, замер времени будет не такой точный, как с помощью JMH, но нам сейчас точные времена не нужны. Нам важно поймать момент, когда всё станет работать в 15 раз медленнее.
  32. Видно, что уже к третьей итерации достигается максимальная производительность около 4 миллисекунд и она поддерживается до 5634-й итерации, после чего скорость резко падает, прямо в один момент, без какого-то переходного периода. При уменьшении параметра max этот неприятный момент наступает на несколько итераций позже, но похоже, что есть некоторый предел в 5000 с лишним итераций. Что может произойти такого с нашей программой после фиксированного числа итераций, не зависящего от размера задачи?
  33. Есть подозрение, JIT-компилятор что-то неудачно перекомпилировал. Давайте посмотрим, чем он вообще занят. Для этого есть опция –XX:+PrintCompilation. С ней в стандартный вывод будет валиться много сообщений вроде приведённых на этом слайде. Если вы никогда эту опцию не использовали, вы можете удивиться, узнав, как много методов компилируется даже для простой программы. Видно причём, что в начале работы компилятор очень напряжённо работает, а потом компиляция почти прекращается. В этом логе легко увидеть имена компилируемых методов, а кроме того есть много дополнительных непонятных цифр и значков. Вообще вывод жёстко не специфицирован и может меняться в разных версиях JVM. В восьмёрке выводится вот так.
  34. Давайте присмотримся к этим буквам и цифрам. Самое левое число – это всего лишь таймстамп, время в миллисекундах с запуска виртуальной машины. Второе число — это номер задачи компиляции в очереди. Иногда они идут не по порядку, более горячий метод может пролезть вперёд других. Потом несколько атрибутов, далее номер уровня tier-компиляции. Потом имя класса и имя метода. Собачка с числом выводится только для on-stack-replacement компиляции и означает позицию в байткоде метода, на которой происходит передача управления. Далее в скобках размер байткода метода в байтах или слово native, если это нативный метод. И в конце может быть ещё какой-нибудь дополнительный статус.
  35. Атрибутов разных всего пять. Нас в данном случае интересует разве что процентик — это как раз признак OSR-компиляции. Остальные — это генерация native-обёртки, признак синхронизации или наличия обработчиков исключений. Флаг b означает, что компиляция выполняется не в фоновом потоке, а блокирует выполнение, в восьмой версии HotSpot с настройками по умолчанию вы его не увидите.
  36. Стоит также упомянуть о tiered-компиляции, или многоуровневой компиляции. Вообще в HotSpot встроено два компилятора: С1 и C2. Исторически Sun, а затем Oracle поставлял две разные виртуальных машины - Client JVM и Server JVM, и вы могли выбирать, какую использовать, с помощью опций -client и -server. Компилятор C2 изначально был в серверной версии, а C1 - в клиентской. Затем оба компилятора стали использоваться вместе в рамках серверной JVM для tiered-компиляции. C1 значительно проще и компилирует быстрее (разница в скорости компиляции может быть на порядок и даже больше), но при этом используется гораздо меньше всяких оптимизаций, и код может работать медленнее. Сейчас в серверной JVM по умолчанию используются оба компилятора, и это контролируется указано в поле comp_level. 0 обычно относится к интерпретируемому коду. В логе компиляции этот уровень встречается для native-методов. Далее идёт C1 с частичным и полным сбором статистики, а уровень 4 соответствует компилятору C2. Обычно из всех уровней от 1 до 3 используется только один на основании разных настроек, размера метода, загруженности очереди компиляции и прочего. Чаще всего метод сразу компилируется на уровень 3. Потом, через некоторое время метод переходит на уровень 4.
  37. Последнее поле – статус или дополнительное сообщение. В случае нормальной компиляции там пусто. Вообще статусов разных бывает много, но чаще всего можно увидеть два. Made non entrant означает, что в ранее скомпилированный метод теперь запрещён вход. Обычно это происходит, если метод перекомпилировали на новом уровне. Например, здесь forEachRemaining был скомпилирован на уровне 4 и ранее скомпилированная версия на уровне 3 помечена not entrant. При этом если скомпилированный метод уже выполняется, он продолжает выполняться: тех, кто успел зайти, не выгоняют.
  38. Иногда вместо made non entrant можно увидеть статус made zombie, который означает, что метод готов к удалению и будет собран сборщиком мусора.
  39. По логу PrintCompilation мы можем судить, когда началась компиляция и когда в ранее скомпилированный метод входить больше нельзя. Хотелось бы явно видеть ещё, когда свежескомпилированный метод готов к выполнению. Для этого пригодится опция TraceNMethodInstalls. Она разбавит лог вот такими простым и и понятными строчками. В скобках номер уровня tier-компиляции, а дальше полная сигнатура метода. Эта опция, как и некоторые другие, которые мы используем, считается диагностической и запрещена по умолчанию. Чтобы её использовать, надо дописать ещё –XX:+UnlockDiagnosticVMOptions.
  40. Итак, знаниями вооружились, добавим опций и посмотрим на вывод нашей программы. Вот, что мы видим в районе 5600. Предыдущие сотни итераций было всё спокойно, ничего не компилировалось, а тут компилятор активизировался. Началось всё с перекомпиляции самого главного метода sumTwiceStream на 4-м уровне. До этого он был скомпилирован на уровне 3 простым компилятором C1 с меньшим количеством оптимизаций. Сейчас же дошло дело до компилятора C2, который обычно делает всё лучше и быстрее. Компиляция, как я говорил, идёт в отдельном потоке, а программа продолжает выполняться. Через три итерации компиляция закончилась, старый скомпилированный компилятором C1 sumTwiceStream был помечен как «not entrant», и была установлена новая версия. Но так как итерация 5634 уже вовсю шла, она доработала со старым sumTwiceStream. А вот следующая началась уже с новым, который оказался не быстрее, как планировалось, а в 15 раз медленнее. Тут мы, кстати, видим ещё один интересный момент — зачем-то сразу после этого компилятором C2 скомпилировалась лямбда, которая отвечает за суммирование. Казалось бы, суммирование мы делали уже миллиарды раз, оно должно было быть давно скомпилировано?
  41. Интересно, почему метод перекомпилировался именно сейчас. Оказывается, среди тысячи параметров HotSpot есть опция, позволяющая управлять тем, сколько запусков метода потребуется для C2-компиляции: это Tier4InvocationThreshold. Tier4, как вы уже знаете, – это внутреннее обозначение C2-компилятора. По умолчанию там 5000, поставьте 4000, и момент истины наступит на 1000 итераций раньше. Остальные 600 с лишним итераций, видимо накопились из различных задержек и не задаются жёстко. Разумеется, не всегда надо вызвать метод 5000 раз, чтобы случилась C2-компиляция: она может наступить и раньше. Например, другой параметр, который её контролирует, - это Tier4BackEdgeThreshold: суммарное количество обратных переходов в методе, о которых мы говорили. Как раз благодаря ему срабатывал OnStackReplacement в нашем первом примере. Почему же крутой мегаоптимизирующий компилятор C2 произвёл код, который в 15 раз медленнее, чем то, что было до этого скомпилировано C1? Баг в компиляторе?
  42. Есть подозрение, что проблема как-то связана с инлайнингом. Инлайнинг — это замечательная способность компиляторов встраивать тела одних методов в другие. К примеру, если бы в нашем простом тесте мы умножение вынесли в отдельный метод, даже компилятор C1 догадался бы подставить тело этого метода в основной. Компилятор C2 же использует инлайнинг гораздо более агрессивно, порой встраивая десятки и даже сотни методов друг в друга. Инлайнинг не только позволяет экономить на вызове метода, но и открывает дорогу десяткам других оптимизаций, которые не могут выглянуть за пределы текущего метода. Как посмотреть, что и куда инлайнится? Разумеется, для этого тоже есть опция JVM — +PrintInlining. Давайте её включим и ещё раз посмотрим.
  43. Ох, страшно-то как.
  44. Это я только маленький кусок привёл для примера. Тут как раз показано, как происходит инлайнинг в forEachRemaining, который скомпилировался в режиме OnStackReplacement где-то в самом-самом начале. Если всё-таки в этом разобраться, можно понять, что заинлайнить удалось вообще всё.
  45. Если совсем страшно, можно в качестве альтернативы использовать такой инструмент JITWatch. К сожалению, у нас не хватит времени, чтобы подробно с ним познакомиться, но вы можете поэкспериментировать сами. Он умеет разбирать лог компиляции HotSpot и в числе прочего рисовать вот такие красивые графики. Вот жёлтые имена методов — это то, что заинлайнилось. Вот наша лямбда, вот суммирование, и можно понять, что в во внутреннем цикле forEachRemaining удалось заинлайнить всё.
  46. Чтобы было ещё понятнее, я нарисовал вот такую картинку. По факту примерно вот так выглядит иерархия вызовов до 5600 итерации. К этому момент C2-компилятор сработал только для forEachRemaining, потому что там было много обратных переходов. Он заинлайнил всё, что можно, и круто соптимизировав всю нижнюю часть, с раскруткой цикла и векторизацией. Всё остальное, включая основной метод sumTwiceStream, скомпилировано пока компилятором C1, который смог только заинлайнить только reduce внутрь sum, а все остальные вызовы остались независимыми методами. Виртуальный вызов C1 редко может заинлайнить. Но это ничего особо не замедляет, потому что время выполнения всей этой цепочки всё равно мало по сравнению с циклом на 10 миллионов итераций. То есть, до 5600-й итерации программа выглядит по факту вот так, и мы измерили производительность вот такого кода.
  47. Теперь посмотрим, что произошло, когда перекомпилировался sumTwiceStream компилятором C2. Опять же на слайде только маленький кусочек лога, по факту про эту перекомпиляцию пишется строчек 100. Мы видим, что C2 играючи инлайнит десятки методов, но в самой самой глубине мы видим неприятную надпись: inlining too deep.
  48. Вот как это выглядит в JITWatch. Там весь метод sumTwiceStream — это огромное дерево, почти сто вызовов удалось заинлайнить внутрь, но вот самые глубокие и самые нужные выводятся чёрным цветом и, если навестись, мы увидим то же самое «Inlining too deep». Просто у JVM есть жёсткий лимит на глубину вызовов, которые в принципе разрешено заинлайнить. По умолчанию этот лимит равен 9 и мы в него упираемся.
  49. Фактически вот какой код мы имеем после итерации 5600 и дальше мы уже измеряем именно его производительность. Заметьте, хотя исходник не менялся, но это по большому счёту уже совсем другая программа, она весьма непохожа на предыдущую. И, к сожалению, гораздо медленнее, потому что теперь основной цикл вызывает на каждой итерации два внешних метода и не может применить другие эффективные оптимизации. Как можно это побороть? Ну правильно, если мы уткнулись в лимит, надо этот лимит увеличить. Для этого, конечно, есть специальная опция JVM – MaxInlineLevel.
  50. Кстати, может некоторые задаются вопросом, откуда я узнал все эти опции. Tier4InvocationThreshold, UseCompiler, UseOnstackReplacement, MaxInlineLevel. Можно, конечно, поискать в интернете, но лучше спросить у самой JVM. Все эти настройки вместе со значениями по умолчанию легко посмотреть, набрав вот такую волшебную строку. Вы получите портянку, в которой почти тысяча опций. Они могут отличаться от версии к версии JVM, поэтому лучше доверять этому списку, чем каким-нибудь статьям в интернете. Все эти опции можно крутить на свой страх и риск. Вообще HotSpot – чрезвычайно настраиваемая штука.
  51. Так что, судя по картинке выше, надо поднять MaxInlineLevel где-то на два. В случае с JMH-бенчмарком на самом деле стек ещё длиннее, потому что включает сам бенчмарк. Перебором я установил, что MaxInlineLevel = 13 хватает. Теперь мы видим, что всё в шоколаде: стрим очень быстр для всех размеров задачи.
  52. Давайте ещё раз глянем на лог по итерациям для миллиона. Справа написано, как было раньше. Вы помните, у нас раньше на третьей итерации всё ломалось. А теперь не ломается, даже наоборот: после C2-перекомпиляции стало чуть быстрее.
  53. По сути дела мы теперь для ста тысяч и для миллиона измерили производительность ещё одной программы, где вообще весь код теста слит в один метод. Для 10 миллионов результаты не поменялись, потому что перекомпилировать не успели. Какая же теперь мораль? Давайте использовать стримы везде, только надо MaxInlineLevel задрать посильнее?
  54. К сожалению, всё не так радужно. Мы упустили одну важную вещь. Внутри реализации стрим-методов обычно написано что-нибудь типа вызвать IntConsumer.accept. Мало ли существует в программе реализаций интерфейса IntConsumer? Откуда JIT знает, какую из них вставить сюда? Неслучайно C2-компиляция отложена на 5000 вызовов. В это время программа не просто работает, а ещё и собирается статистика, как конкретно она работает – это называется type profile. В случае наших тестов собралась такая информация, что в 100% случаев в map-операции использовалась лямбда, умножающая на два, а в reduce-операции использовалось Long::sum. Такие вызовы называются мономорфными: хотя в данном месте потенциально может быть вызвана любая функция, статистика говорит, что всегда вызывалась конкретная, поэтому компилятор предполагает, что и дальше будет то же самое, и инлайнит эту конкретную функцию. Это работает в нашем чистеньком бенчмарке, но вряд ли в реальном приложении мы будем использовать Stream API только в одном месте. По факту мы измерили производительность в идеальных условиях, которые редко достижимы на практике.
  55. Давайте сэмулируем загрязнение профиля в нашем бенчмарке. Добавим ещё один параметр pollute и отдельный метод setup, который выполняется один раз перед каждым бенчмарком. При pollute = 0 мы ничего не делаем, а при 1, 2 и 3 соответствующее количество раз выполняем вот такой бесполезный цикл с разными лямбдами. Заметьте, тут case без break, поэтому, например, для pollute = 3 выполняются все ветки. Обращаю особое внимание: сами тесты мы никак не меняли. Глобальное состояние программы мы вроде как тоже не меняли. Этот код просто выполнится один раз перед всеми тестами, один раз, и как будто бы никаких побочных эффектов не создаёт. А потом мы делаем всё те же 5 разогревочных итераций и 10 основных.
  56. Тут я уже не привожу результаты традиционной реализации: они от значения pollute не меняются. А вот что происходит со стримами. Одинарный удар в челюсть компилятор переносит с честью: замедление наблюдается, но сравнительно небольшое. В таком случае в профиле два возможных варианта, компилятор инлайнит оба, добавляя проверку типа. Такой вызов называется биморфным. А вот второй удар в челюсть укладывает компилятор на обе лопатки: если вариантов больше двух, такой вызов становится полиморфным и инлайнинг выключается совсем, несмотря на увеличенный MaxInlineLevel. Дальнейшее увеличение pollute жизнь особо хуже не делает, она и так уже испорчена.
  57. Вот какую программу мы получили в этом случае: reduce всё ещё встраивается, но map-функция теперь не инлайнится, а вызывается отдельно, и это поломало всю оптимизацию цикла. Если загрязнить профиль разными reduce-операциями, будет ещё больше отдельных методов. В реальном большом приложении, где активно используются стримы для разных задач, скорее всего будет что-то подобное.
  58. Вообще загрязнение профиля типов — большая головная боль для разработчиков виртуальной машины. Если кому интересно, вот пара багов на тему. Возможно, в девятой джаве или в апдейтах к ней что-нибудь изменится, но пока увы вот так. Так что же, какова мораль? Всё-таки не будем использовать стримы?
  59. На самом деле мы всю дорогу игнорировали одну тривиальную вещь: эту задачу можно решить гораздо быстрее и вообще за константное время на много порядков быстрее. Смысл бороться за 4 миллисекунды, когда задача решается за 3 наносекунды? Эта задача была слишком искусственной. Дело не в том, что стрим на порядок медленнее, а в том, что при полном инлайнинге простой алгоритм с суммированием и умножением оптимизируется очень круто. Всё, что мы измерили, не имеет отношения к реальным задачам, за которые обычно платят деньги. На реальных задачах стрим редко проигрывает больше, чем вдвое, если только задача не настолько быстрая, чтобы это вообще кого-то волновало. Чаще различие составляет не более десятков процентов и редко является узким местом в производительности. Поэтому смело пользуйтесь Stream API.
  60. Не стоит печалиться, что наша задача слишком искусственная. Полученные нами цифры на практике бесполезны, но зато очень полезны полученные знания. Мы глубоко копнули HotSpot, познакомились с добрым десятком опций, как чисто диагностических (справа), так и влияющих на работу виртуальной машины. Мы немного научились читать и интерпретировать диагностику и познакомились с полезными инструментами — JMH и JITWatch. Мы увидели, что один и тот же исходный Java-код на этапе выполнения может совершенно причудливо меняться в зависимости от того, сколько раз он выполнялся до этого, какой ещё другой код до этого выполнялся, с какими опциями запущена виртуальная машина и так далее. А главное, мы поняли, что взять хороший вольтметр недостаточно, чтобы получить хороший результат. Здесь, как и в физике, правильная постановка эксперимента так же важна, как и правильные приборы. В целом не бойтесь придумывать искусственные задачи: пусть они не имеют отношения к реальному продакшн-коду, зато помогают глубже понять, как работает язык. Ещё маленькое замечание насчёт опций JVM. Их очень много, почти тысяча, но это не значит, что надо их слепо крутить в продакшне на основании результатов одного бенчмарка, а тем более на основании советов в интернете. Помните, что опции влияют на всё приложение, а значения по умолчанию аккуратно подобраны умными людьми. Будьте осторожны.
  61. Для дальнейшего изучения вопросов, связанных с вызовом методов и инлайнингом в JVM, почитайте статью Алексея Шипилёва «The Black Magic of Java Method Dispatch». Она весьма сурова и рассчитана на продвинутого читателя, но очень интересна. Кому это покажется слишком сурово, можете посмотреть выступление разработчика HotSpot Владимира Иванова «Динамическая JIT-компиляция в JVM», оно попроще будет. Насчёт того как правильно замерять производительность Java-кода: если вы ещё не слушали выступление Алексея с прошлого Joker’а «Как два таймстампа прочитать», обязательно посмотрите или почитайте текстовую версию на английском. Относительно же странных эффектов, рассмотренных в моём докладе, стоит взглянуть на ответ Пола Сандоза, одного из разработчиков стрим API. В приведённом вопросе на StackOverflow разбирается похожий случай.
  62. Всем спасибо за внимание, быстрых программ, добра и обнимашек!