Ответ 1
TL; DR: вы не должны накладывать доверие BLIND.
Прежде всего, сначала нужно проверить экспериментальные данные, прежде чем перейти к выводам из них. Просто утверждать, что что-то в 3 раза быстрее/медленнее, странно, потому что вам действительно нужно следить за причиной разницы в производительности, а не просто доверять номерам. Это особенно важно для нано-тестов, как у вас.Во-вторых, экспериментаторы должны четко понимать, что они контролируют, а какие нет. В вашем конкретном примере вы возвращаете значение из методов @Benchmark
, но можете ли вы быть разумно уверены, что вызывающие внешние пользователи будут делать то же самое для примитива и ссылки? Если вы зададите себе этот вопрос, тогда вы поймете, что вы в основном измеряете инфраструктуру тестирования.
До точки. На моей машине (i5-4210U, Linux x86_64, JDK 8u40) тест дает:
Benchmark (value) Mode Samples Score Error Units
...benchmarkReturnOrdinal 3 thrpt 5 0.876 ± 0.023 ops/ns
...benchmarkReturnOrdinal 2 thrpt 5 0.876 ± 0.009 ops/ns
...benchmarkReturnOrdinal 1 thrpt 5 0.832 ± 0.048 ops/ns
...benchmarkReturnReference 3 thrpt 5 0.292 ± 0.006 ops/ns
...benchmarkReturnReference 2 thrpt 5 0.286 ± 0.024 ops/ns
...benchmarkReturnReference 1 thrpt 5 0.293 ± 0.008 ops/ns
Хорошо, поэтому эталонные тесты появляются на 3 раза медленнее. Но подождите, он использует старый JMH (1.1.1), пусть обновляется до последней версии (1.7.1):
Benchmark (value) Mode Cnt Score Error Units
...benchmarkReturnOrdinal 3 thrpt 5 0.326 ± 0.010 ops/ns
...benchmarkReturnOrdinal 2 thrpt 5 0.329 ± 0.004 ops/ns
...benchmarkReturnOrdinal 1 thrpt 5 0.329 ± 0.004 ops/ns
...benchmarkReturnReference 3 thrpt 5 0.288 ± 0.005 ops/ns
...benchmarkReturnReference 2 thrpt 5 0.288 ± 0.005 ops/ns
...benchmarkReturnReference 1 thrpt 5 0.288 ± 0.002 ops/ns
Ой, теперь они только медленнее. Кстати, это также говорит нам, что тест связан с инфраструктурой. Хорошо, можем ли мы увидеть, что на самом деле происходит?
Если вы создадите тесты и посмотрите, что именно называет ваши методы @Benchmark
, вы увидите что-то вроде:
public void benchmarkReturnOrdinal_thrpt_jmhStub(InfraControl control, RawResults result, ReturnEnumObjectVersusPrimitiveBenchmark_jmh l_returnenumobjectversusprimitivebenchmark0_0, Blackhole_jmh l_blackhole1_1) throws Throwable {
long operations = 0;
long realTime = 0;
result.startTime = System.nanoTime();
do {
l_blackhole1_1.consume(l_longname.benchmarkReturnOrdinal());
operations++;
} while(!control.isDone);
result.stopTime = System.nanoTime();
result.realTime = realTime;
result.measuredOps = operations;
}
Этот l_blackhole1_1
имеет метод consume
, который "потребляет" значения (см. Blackhole
для обоснования). Blackhole.consume
имеет перегрузки для ссылки и примитивы, и что один достаточно, чтобы оправдать разницу в производительности.
Существует логическое обоснование того, почему эти методы выглядят по-разному: они стараются быть как можно быстрее для своих типов аргументов. Они не обязательно демонстрируют одни и те же характеристики производительности, хотя мы стараемся их сопоставить, следовательно, более симметричный результат с более новым JMH. Теперь вы можете даже перейти к -prof perfasm
, чтобы увидеть сгенерированный код для своих тестов и посмотреть, почему производительность отличается, но это не следует здесь.
Если вы действительно хотите понять, как возвращение примитива и/или ссылки отличается по производительности, вам нужно будет ввести большую страшную серо-зону для точной оценки производительности. Например. что-то вроде этого теста:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(5)
public class PrimVsRef {
@Benchmark
public void prim() {
doPrim();
}
@Benchmark
public void ref() {
doRef();
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
private int doPrim() {
return 42;
}
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
private Object doRef() {
return this;
}
}
..., который дает тот же результат для примитивов и ссылок:
Benchmark Mode Cnt Score Error Units
PrimVsRef.prim avgt 25 2.637 ± 0.017 ns/op
PrimVsRef.ref avgt 25 2.634 ± 0.005 ns/op
Как я сказал выше, эти тесты требуют рассмотрения причин для результатов. В этом случае сгенерированный код для обоих значений почти одинаковый, и это объясняет результат.
чопорная:
[Verified Entry Point]
12.69% 1.81% 0x00007f5724aec100: mov %eax,-0x14000(%rsp)
0.90% 0.74% 0x00007f5724aec107: push %rbp
0.01% 0.01% 0x00007f5724aec108: sub $0x30,%rsp
12.23% 16.00% 0x00007f5724aec10c: mov $0x2a,%eax ; load "42"
0.95% 0.97% 0x00007f5724aec111: add $0x30,%rsp
0.02% 0x00007f5724aec115: pop %rbp
37.94% 54.70% 0x00007f5724aec116: test %eax,0x10d1aee4(%rip)
0.04% 0.02% 0x00007f5724aec11c: retq
ссылка
[Verified Entry Point]
13.52% 1.45% 0x00007f1887e66700: mov %eax,-0x14000(%rsp)
0.60% 0.37% 0x00007f1887e66707: push %rbp
0.02% 0x00007f1887e66708: sub $0x30,%rsp
13.63% 16.91% 0x00007f1887e6670c: mov %rsi,%rax ; load "this"
0.50% 0.49% 0x00007f1887e6670f: add $0x30,%rsp
0.01% 0x00007f1887e66713: pop %rbp
39.18% 57.65% 0x00007f1887e66714: test %eax,0xe3e78e6(%rip)
0.02% 0x00007f1887e6671a: retq
[сарказм] Посмотрите, как легко! [/Сарказм]
Образец: чем проще вопрос, тем больше вы должны работать, чтобы сделать правдоподобный и надежный ответ.