Neden Java nesne başvurusu bir ilkel dönen daha yavaş dönüyor | Netgez.com
SORU
6 NÄ°SAN 2015, PAZARTESÄ°


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
6 NÄ°SAN 2015, PAZARTESÄ°


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.

Bunu PaylaÅŸ:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VÄ°DEO

Rastgele Yazarlar

  • Floortile83

    Floortile83

    16 Ocak 2010
  • Vsauce

    Vsauce

    30 Temmuz 2007
  • whatever

    whatever

    30 EYLÃœL 2005