Neden Java nesne başvurusu bir ilkel dönen daha yavaş dönüyor
Gecikmeli hassas bir uygulama üzerinde çalışıyoruz ve yöntemleri (jmh kullanarak) her türlü microbenchmarking. Bir arama yöntemi microbenchmarking ve sonuçtan memnun kaldıktan sonra, son sürüm, son sürüm olduğunu bulmak için uygulanmaktadır3 kat daha yavaşsadece karşılaştırılan vardı.
Suçlu uygulanan yöntem int
yerine enum
bir nesne döndürmek oldu. Burada kriter kodunu basit bir versiyonu:
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class ReturnEnumObjectVersusPrimitiveBenchmark {
enum Category {
CATEGORY1,
CATEGORY2,
}
@Param( {"3", "2", "1" })
String value;
int param;
@Setup
public void setUp() {
param = Integer.parseInt(value);
}
@Benchmark
public int benchmarkReturnOrdinal() {
if (param < 2) {
return Category.CATEGORY1.ordinal();
}
return Category.CATEGORY2.ordinal();
}
@Benchmark
public Category benchmarkReturnReference() {
if (param < 2) {
return Category.CATEGORY1;
}
return Category.CATEGORY2;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(ReturnEnumObjectVersusPrimitiveBenchmark.class.getName()).warmupIterations(5)
.measurementIterations(4).forks(1).build();
new Runner(opt).run();
}
}
Yukarıdaki kıyaslama sonuçları:
# VM invoker: C:\Program Files\Java\jdk1.7.0_40\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
Benchmark (value) Mode Samples Score Error Units
benchmarkReturnOrdinal 3 thrpt 4 1059.898 ± 71.749 ops/us
benchmarkReturnOrdinal 2 thrpt 4 1051.122 ± 61.238 ops/us
benchmarkReturnOrdinal 1 thrpt 4 1064.067 ± 90.057 ops/us
benchmarkReturnReference 3 thrpt 4 353.197 ± 25.946 ops/us
benchmarkReturnReference 2 thrpt 4 350.902 ± 19.487 ops/us
benchmarkReturnReference 1 thrpt 4 339.578 ± 144.093 ops/us
Sadece dönüş fonksiyon türü değişen değişti neredeyse 3 kat performans.
Bir tamsayı bir 64-bit değeri (referans) verir karşı numaralama bir nesne döndürmek arasındaki tek fark diğeri 32 bit bir değer verir diye düşündüm. Meslektaşlarımdan biri numaralama dönen bir ek yük ilave potansiyel GC başvurusunu takip etmek için ihtiyaç tahmin edildi. (Ama numaralama nesneleri statik final kaynaklar göz önüne alındığında, bunu yapmak zorunda olacağını garip görünüyor).
Performans farkı izahı nedir?
GÃœNCELLEME
Herkes klon ve kriter çalıştırabilirsiniz maven projesi here Bu yüzden paylaştım. Eğer herhangi biri ya da faiz varsa, Eğer diğerleri de aynı sonuçları çoğaltmak için yararlı olacaktır. (2 farklı makineler, Windows 64 ve Linux 64, Oracle Java 1.7 Jvm'ler de kullanarak tatlar) sentezledim. @ZhekaKozlov diyor yöntemleri arasında herhangi bir fark görmedim.
Çalıştırmak için: (depo klonlama sonra)
mvn clean install
java -jar .\target\microbenchmarks.jar function.ReturnEnumObjectVersusPrimitiveBenchmark -i 5 -wi 5 -f 1
CEVAP
TL;DR: her şeye KÖR bir güven koymak gerekir.
İlk nokta şudur: önemli onları gelen sonuçlara atlama önce deneysel verileri kontrol etmek gerekir. Bir şey 3x daha yavaş/daha hızlı olduğunu sadece iddia gerçekten performans farkı nedeni takip değil, sadece sayıları güvenmek gerekir, çünkü çok garip. Bu gibi nano-kriterler için özellikle önemlidir.
İkinci olarak, deneyciler açıkça onlar kontrol ne ve ne olduğunu anlamak gerekir. Belirli bir örnekte, @Benchmark
yöntemler değeri dönen, ama oldukça emin olmak arayanların ve başvuru ilkel için aynı şeyi yapacağım dışında olabilir? Eğer bu soruyu kendinize sorarsanız, o zaman temelde test altyapı ölçüm olduğunuzu fark edeceksiniz.
Sahile. Benim makinede (- ı5 4210U, Linux x86_64, FIRSATLAR 8u40), test verim:
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
Tamam, referans testleri 3x daha yavaş görünür. Ama bekleyin, eski bir JMH (1.1.1) kullanır, geçerli güncelleme yapalım en son (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
Ayy, şimdi sadece zar zor yavaş. BTW, bu da test altyapısına bağlı olduğunu söyler. Tamam, gerçekten ne görüyoruz?
Kriterler oluÅŸturmak ve tam olarak @Benchmark
yöntemlerinizi dediği etrafına bir bak, o zaman bir şey gibi görürsünüz.
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
"tüketir" değerleri (gerekçesi için Blackhole
). consume
bir yöntemi vardır, Blackhole.consume
references* *32, ve yalnız performans farkı haklı çıkarmak için yeterli olduğu için aşırı vardır.
Bu yöntemler farklı görünüyor neden bir gerekçesi var: mümkün olduğunca hızlı bir argüman. kendi türleri için çalışıyorlar Mutlaka onları maç çalışsak da aynı performans özelliklerini gösteren, dolayısıyla daha simetrik daha yeni JMH ile sonuçlanır. Şimdi, hatta -prof perfasm
testleriniz için oluşturulan kodu görmek ve performansını neden farklı olduğunu görebilirsiniz, ama bu noktayı burada.
Eğer gerçekten varsaistiyorumilkel ve/veya referans dönen performans açısından ne kadar farklı olduğunu anlamak için, bir girmeniz gerekiriri, korkunç, gri bölgenüanslı performans kıyaslama. E. g. bu test gibi bir şey:
@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;
}
}
ilkel ve başvurular için: aynı sonucu verir
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
Yukarıda dediğim gibi, bu testlergerektirirsonuçların sebepleri takip ediyorum. Bu durumda, her ikisi için oluşturulan kod hemen hemen aynı ve sonucu açıklar.
prim:
[Verified Entry Point]
12.69% 1.81% 0x00007f5724aec100: mov êx,-0x14000(%rsp)
0.90% 0.74% 0x00007f5724aec107: push %rbp
0.01% 0.01% 0x00007f5724aec108: sub $0x30,%rsp
12.23% 16.00% 0x00007f5724aec10c: mov $0x2a,êx ; load "42"
0.95% 0.97% 0x00007f5724aec111: add $0x30,%rsp
0.02% 0x00007f5724aec115: pop %rbp
37.94% 54.70% 0x00007f5724aec116: test êx,0x10d1aee4(%rip)
0.04% 0.02% 0x00007f5724aec11c: retq
ref:
[Verified Entry Point]
13.52% 1.45% 0x00007f1887e66700: mov êx,-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 êx,0xe3e78e6(%rip)
0.02% 0x00007f1887e6671a: retq
[alay] ne kadar kolay olduğunu Görmek! [/sarcasm]
Desen: basit bir soru daha akla yatkın ve güvenilir bir cevap yapmak için çalışmak zorunda.
Neden bir döngü iki döngü daha yavaş g...
Neden bitişik in Java daha hızlı geçiş...
Neden bazı yüzer < karşılaştırmalar...
Neden sıralanmış bir dizi sıralanmamış...
Neden 512x512 matrix 513x513 bir matri...