Java'da instanceof kullanmanın performans etkisi


314

Bir uygulama üzerinde çalışıyorum ve bir tasarım yaklaşımı instanceofoperatörün aşırı derecede ağır kullanımını içeriyor . OO tasarımının genellikle kullanmaktan kaçınmaya çalıştığını bilsem de instanceof, bu farklı bir hikaye ve bu soru tamamen performansla ilgilidir. Performans etkisi olup olmadığını merak ediyordum? O kadar hızlı ==mı?

Örneğin, 10 alt sınıflı bir temel sınıfım var. Temel sınıfı alan tek bir işlevde, sınıfın alt sınıfın bir örneği olup olmadığını denetler ve bazı rutin yürütürüm.

Bunu çözmeyi düşündüğüm diğer yollardan biri, bunun yerine "type id" tamsayı ilkelini kullanmak ve alt sınıfların kategorilerini temsil etmek için bir bitmask kullanmak ve sonra yalnızca "type id" alt sınıflarının biraz maske karşılaştırması yapmaktı. kategoriyi temsil eden sabit maske.

Bir instanceofşekilde JVM tarafından bundan daha hızlı olacak şekilde optimize edilmiş mi? Java'ya bağlı kalmak istiyorum ancak uygulamanın performansı kritik. Daha önce bu yolda olan birinin bazı tavsiyelerde bulunabilmesi harika olurdu. Çok fazla nitelendiriyorum veya optimize etmek için yanlış şeye mi odaklanıyorum?


81
Bence sorunun amacı en iyi OO uygulaması sorununu bir kenara bırakmak ve performansı incelemekti.
Dave L.

3
@Dave L. Normalde kabul ediyorum, ancak OP bazı genel optimizasyon teknikleri aradığından bahsetti ve sorununun 'instanceof' ile ilgili olup olmadığından emin değil. Bence en azından 'doğru' tasarımdan bahsetmeye değer, böylece her iki seçeneği de profillendirebilir.
Yasadışı Programcı

51
Ugh ... Neden tüm cevaplar sorunun cevabını kaçırıyor ve optimizasyon konusunda aynı eski Knuth söylemini sağlıyor? Sorunuz instanceof'in == ile sınıf nesnesini kontrol etmekten önemli / şaşırtıcı derecede yavaş olup olmadığı ve bunun olmadığını buldum.
gubby

3
Anlık ve döküm performansı oldukça iyidir. Java7'de, buradaki soruna farklı yaklaşımlar etrafında bazı zamanlama yayınladım: stackoverflow.com/questions/16320014/…
Wheezil

Yanıtlar:


268

Modern JVM / JIC derleyicileri, örnekleme, özel durum işleme, yansıma vb.

Donald Knuth'un yazdığı gibi, "Zamanın yaklaşık% 97'sini küçük verimlilikleri unutmalıyız: erken optimizasyon tüm kötülüklerin köküdür." İnstanceof'in performansı muhtemelen bir sorun olmayacaktır, bu yüzden sorunun bundan emin olana kadar egzotik geçici çözümler bulmak için zamanınızı boşa harcamayın.


13
Modern JVM / JIC .. Lütfen bu optimizasyonun hangi java versiyonundan bahsedildiğini söyleyebilir misiniz?
Ravisha

138
Her zaman performans konu olduğunda Knuth'a atıfta bulunan biri var ... Unutmak, Knuth'un da (aynı makalede) belirtti. "hemen hemen tüm çalışmaları algoritmaların verimliliği ile ilgiliydi ve (diğerlerinin yanı sıra) daha iyi performans elde etmek için montaj algoritmaları yazdı. Meh ...
kgadek

4
Burada bir kenara ama daha try { ObjT o = (ObjT)object } catch (e) { no not one of these }hızlı daha yavaş olurdu?
peterk

35
"Nesne" bir ObjT örneğiyse, bunu oluşturmak bir örnek oluşturmaktan biraz daha hızlıdır, ancak hızlı testimin bulduğu fark 10.000.000 yinelemenin üzerinde 10-20 ms idi. Bununla birlikte, "nesne" bir ObjT değilse, istisnayı yakalamak 3000 kattan daha yavaştı - 31.000 ms'nin üzerinde ~ 10 ms'nin üzerinde.
Steve

19
herhangi bir "referans" olmadan böylesine güçlü bir argüman sadece yararsız olduğu için tamamen işe yaramaz.
marcorossi

279

Yaklaşmak

Farklı uygulamaları değerlendirmek için bir değerlendirme programı yazdım :

  1. instanceof uygulama (referans olarak)
  2. soyut bir sınıf ve @Overridebir test yöntemi ile yönlendirilmiş nesne
  3. kendi türünde bir uygulama kullanma
  4. getClass() == _.class uygulama

Ben 100 m ısınma çağrıları, ölçüm altında 1000 iterasyon ve 10 çatal ile karşılaştırmak için jmh kullandım . Bu yüzden her seçenek 10.000 kez ölçüldü, bu da MacBook Pro'mdaki tüm karşılaştırmayı macOS 10.12.4 ve Java 1.8 ile çalıştırmak için 12:18:57 sürdü. Kıyaslama her bir seçeneğin ortalama süresini ölçer. Daha fazla ayrıntı için GitHub'daki uygulamamı inceleyin .

Tamlık uğruna: Bu cevabın ve benim karşılaştırmamın önceki bir versiyonu var .

Sonuçlar

| Operasyon | İşlem başına nanosaniye cinsinden çalışma süresi | İnstanceof'a göre |
| ------------ | ------------------------------------ - | ------------------------ |
| KURUMSAL | 39.598 ± 0,022 ns / op | % 100,00 |
| GETCLASS | 39,687 ± 0,021 ns / op | % 100,22 |
| TÜR | 46.295 ± 0,026 ns / op | % 116,91 |
| OO | 48.078 ± 0,026 ns / op | % 121,42 |

tl; Dr.

Java 1.8'de çok yakın instanceofolmasına rağmen en hızlı yaklaşımdır getClass().


58
+0.(9)Bilim için!

16
+ diğer benden 0.1: D
Tobias Reich

14
@TobiasReich Öyleyse aldık +1.0(9). :)
Pavel

9
Bunun anlamlı bir şey ölçtüğünü sanmıyorum. Kod System.currentTimeMillis(), tek bir yöntem çağrısından çok daha fazla olmayan bir işlemde kullanımı ölçer ve bu da çok düşük hassasiyet sağlar. Bunun yerine JMH gibi bir karşılaştırma çerçevesi kullanın!
Lii

6
Veya çağrı başına değil, milyarlarca çağrının zamanlamasını yapın.
LegendLength

74

Ben sadece exampleOf performans basit bir s.equals () çağrı sadece bir harfli bir dize nesnesiyle nasıl karşılaştırdığını görmek için basit bir test yaptım.

10.000.000 döngüde örnek bana 63-96ms verdi ve dize eşittir 106-230ms verdi

Java jvm 6 kullandım.

Yani benim basit testte bir karakter dizisi karşılaştırması yerine bir exampleOf yapmak daha hızlıdır.

dize yerine Integer's .equals () kullanmak bana aynı sonucu verdi, ancak == i kullanıldığında 20ms kadar 20O'ya kadar (10.000.000 döngüde)


4
Kodu buraya göndermeniz mümkün müdür? Bu harika olurdu!
Simyacı

7
İnstanceOf, polimorfik fonksiyon gönderimi ile nasıl karşılaştırıldı?
Chris

21
Neden instanceof öğesini bir String.equals () ile karşılaştırıyorsunuz? Nesneyi kontrol etmek istiyorsanız, object.getClass (). Eşittir (SomeType.class)
marsbear

4
@marsbear equals()kesmeyecek, çünkü alt sınıf; ihtiyacınız var isAssignableFrom().
David Moles

1
@marsbear Doğru, ama bu OP'nin ne istediğine dair daha iyi bir test değil.
David Moles

20

Performans etkisini belirleyecek maddeler şunlardır:

  1. İnstanceof operatörünün true olarak dönebileceği olası sınıfların sayısı
  2. Verilerinizin dağıtımı - ilk veya ikinci denemede örnek olayların çoğu çözüldü mü? Öncelikle gerçek operasyonları geri döndürme olasılığınızı en yüksek düzeye koymak isteyeceksiniz.
  3. Dağıtım ortamı. Sun Solaris VM üzerinde çalıştırmak Sun'ın Windows JVM'sinden önemli ölçüde farklıdır. Solaris varsayılan olarak 'sunucu' modunda, Windows ise istemci modunda çalışır. Solaris'teki JIT optimizasyonları, tüm yöntem erişimini aynı hale getirecektir.

Dört farklı gönderim yöntemi için bir mikrobenchmark oluşturdum . Solaris'in sonuçları aşağıdaki gibidir ve daha küçük sayı daha hızlıdır:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

18

Son sorunuza cevap veriniz: Bir profil oluşturucu size söylemediği sürece, bir örnekle saçma zaman harcadığınızı gösterir: Evet, alaycı davranıyorsunuz.

Hiçbir zaman optimize edilmesi gerekmeyen bir şeyi optimize etmeyi merak etmeden önce: Algoritmanızı en okunabilir şekilde yazın ve çalıştırın. Jit-derleyici kendisini optimize etme şansı elde edene kadar çalıştırın. Daha sonra bu kod parçasıyla ilgili sorun yaşarsanız, size en fazla nereyi elde edeceğinizi ve bunu optimize edeceğinizi söylemek için bir profil oluşturucu kullanın.

Son derece optimize edilmiş derleyiciler zaman zaman, darboğazlarla ilgili tahminleriniz tamamen yanlış olacaktır.

Ve bu cevabın gerçek ruhu ile (ki yürekten inanıyorum): jit-derleyicinin onu optimize etme şansı olduğunda instanceof ve == ilişkilerini kesinlikle bilmiyorum.

Unuttum: Asla ilk koşuyu ölçmeyin.


1
Ancak bahsi geçen orijinal poster bu uygulama için kritikti, bu nedenle bu durumda erken optimizasyon yapmak mantıksız değil. Başka bir deyişle, GWBasic'de bir 3d oyun yazmazsınız ve sonunda sonunda, bunu optimize etmeye başlayalım, ilk adım c ++ 'a taşımaktır.
LegendLength

GWBasic, uygun kütüphaneler varsa, 3d oyunlar için harika bir başlangıç ​​olabilir. Ancak bu bir yana (yapay bir argüman olduğu için): OP, optimizasyon olarak tam bir yeniden yazma istemiyor. Etkinin önemli olup olmadığını bile bilmediğimiz tek bir yapı hakkında ( derleyicinin mevcut sürümünde aynı şeyi yapmanın daha iyi bir yolu olsa bile ). Sıkıca c2.com/cgi/wiki?ProfileBeforeOptimizing ve cevabımın arkasında duruyorum . Ön Optimizasyon tüm kötülüklerin köküdür! Bakımı zorlaştırır - ve bakım optimize
Olaf Kock

15

Aynı sorum var, ama benimkine benzer kullanım durumu için 'performans metrikleri' bulamadım, biraz daha örnek kod yaptım. Donanımımda ve Java 6 ve 7'de, instanceof ve 10mln yinelemelerini açma arasındaki fark

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

Bu nedenle, özellikle çok sayıda if-else-if ifadesinde instanceof gerçekten daha yavaştır, ancak gerçek uygulamada fark göz ardı edilebilir.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}

Hangi sonuç java 6 ve hangisi java 7 idi? Bunu Java 8 altında yeniden ziyaret ettiniz mi? Daha da önemlisi, eğer instanceofs ise bir uzunluğu ints ile ilgili bir vaka bildirimi ile karşılaştırırsınız. Bence int anahtarının hızla aydınlanmasını bekleriz.
Azeroth2b

1
5 yıl önce ne olduğunu tam olarak hatırlayamıyorum - hem Java 6 hem de Java 7'nin benzer bir sonucu olduğunu düşünüyorum, bu yüzden sağlanan tek bir sonuç var (2 satırın farklı sınıf hiyerarşisi derinliği için olması şartıyla) ... , Java 8 ile karşılaştırma deneyin vermedi. Tüm test kodu sağlanır - sen kopyala / yapıştır ve ihtiyacınız olan ortamlarda kontrol (not - bugünlerde bunun için JMH testi kullanmak istiyorsunuz).
Xtra Coder

9

instanceof gerçekten hızlıdır ve yalnızca birkaç CPU talimatı alır.

Görünüşe göre, bir sınıfta Xyüklü alt sınıf yoksa (JVM bilir), instanceofşu şekilde optimize edilebilir:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

Ana maliyet sadece bir okuma!

Yüklü Xalt sınıflar varsa , birkaç okuma daha gerekir; büyük olasılıkla ortak konumlandırıldığından ekstra maliyet de çok düşüktür.

Herkese iyi haberlerimiz var!


2
olabilir optimize edilmesi veya edilir optimize? kaynak?

@vaxquis jvm impl spesifik olarak olabilir
RecursiveExceptionException

@itzJanuary nefes herkes derleyici bilir: Buraya sorumu noktasını cevapsız olabilir optimizefoo - ama edilmektedir fooaslında şu anda Oracle'ın Javac / VM tarafından optimize - ya da gelecekte bunu yapacağım bu sadece mümkün mü? Ayrıca, soruyu yanıtlayana sordum gerçekten optimize edilebilir veya optimize olduğunu belgeleyen o herhangi destek kaynağını (bu dokümanlar, kaynak kod, dev blog olabilir) var mı ? Bu olmasa cevap derleyici nedir hakkında sadece bazı rastgele esin olduğunu olabilir belki yapmak.

@ vaxquis Hotspot VM'den hiç bahsetmediniz, ancak bu durumda "optimize edilmiş" olup olmadığını bilmiyorum.
RecursiveExceptionException

1
Son zamanlarda JIT'in (JVM 8) bir çağrı sitesini doğrudan çağrılarla 1 veya 2 tür için optimize edeceğini, ancak ikiden fazla gerçek türle karşılaşıldığında vtable'a döneceğini okuyun. Bu nedenle, çalışma zamanında bir çağrı sitesinden geçen sadece iki beton türünün olması bir performans avantajı sağlar.
simon.watts

5

Instanceof çok hızlı. Sınıf referansı karşılaştırması için kullanılan bir bayt koduna kadar kaynar. Birkaç milyon instanceof'u bir döngüde deneyin ve kendiniz görün.


5

instanceof muhtemelen çoğu gerçek dünya uygulamasında basit bir eşitlikten daha maliyetli olacaktır (yani, instanceof'ın gerçekten gerekli olduğu yerlerdir ve her başlangıç ​​ders kitabı gibi ortak bir yöntemi geçersiz kılarak çözemezsiniz. Demian yukarıdaki öner).

Neden? Muhtemelen gerçekleşecek olan, bazı işlevler (diyelim ki x, y ve z arabirimleri) sağlayan ve bu arabirimlerden birini uygulayabilen (veya uygulayamayan) bazı nesneler sağlayan birkaç arabiriminizin olması ... dolaylı. Diyelim ki, bende var:

w x'i uzatır

A uygular w

B, A'yı genişletir

C, B'yi genişletir, y'yi uygular

D, C'yi genişletir, z'yi uygular

D örneğini, d nesnesini işlediğimi varsayalım. Hesaplama (d instanceof x) d.getClass () almak, == to x olup olmadığını bilmek için uyguladığı arabirimler arasında döngü gerektirir ve eğer değilse tüm ataları için yinelemeli olarak tekrar yapmaz ... Bizim durumumuzda, o ağacın ilk genişliğini yaparsanız, y ve z'nin hiçbir şeyi genişletmediğini varsayarak, en az 8 karşılaştırma yaparsınız ...

Gerçek dünyadan türetilmiş bir ağacın karmaşıklığının daha yüksek olması muhtemeldir. Bazı durumlarda, JIT, olası tüm durumlarda, x'i genişleten bir şeyin örneği olarak önceden çözülebiliyorsa, çoğunu optimize edebilir. Bununla birlikte, gerçekçi olarak, çoğu zaman o ağaç geçişinden geçeceksiniz.

Bu bir sorun haline gelirse, nesnenin somut sınıfını işlemeyi yapan bir kapağa bağlayarak bunun yerine bir işleyici haritası kullanmanızı öneririm. Doğrudan bir eşleme lehine ağaç geçiş aşamasını kaldırır. Ancak, C.class için bir işleyici ayarladıysanız, yukarıdaki d nesnemin tanınmayacağını unutmayın.

İşte 2 sentim, umarım yardımcı olurlar ...


5

instanceof çok verimlidir, bu nedenle performansınızın zarar görmesi olası değildir. Ancak, çok sayıda instanceof kullanmak bir tasarım sorunu olduğunu düşündürmektedir.

XClass == String.class kullanabiliyorsanız, bu daha hızlıdır. Not: final dersleri için instanceof komutuna ihtiyacınız yoktur.


1
Btw "son sınıflar için örneklemeye gerek yok" ile ne demek istiyorsun?
Pacerier

Bir final sınıfının alt sınıfları olamaz. Bu durumda x.getClass() == Class.classaynıdırx instanceof Class
Peter Lawrey

Güzel, x'in boş olmadığını varsayarsak hangisini tercih edersin?
Pacerier

İyi bir nokta. Ben bekliyoruz olmasına bağlı olacağını xolmak nullherhalde. (Veya hangisi daha
açıksa

Hmm java.lang.class.isAssignableFrom'u da kullanabileceğimizi fark ettim, instanceof anahtar kelimesinin dahili olarak bu gibi işlevler kullanıp kullanmadığını biliyor musunuz?
Pacerier

4

Genellikle böyle bir durumda "instanceof" operatörünün kaşlarını çatmasının nedeni (instanceof'in bu temel sınıfın alt sınıflarını kontrol ettiği yerlerde) yapmanız gereken, işlemleri bir yönteme taşımak ve uygun olan için geçersiz kılmaktır. alt sınıfları. Örneğin, aşağıdakilere sahipseniz:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Bunu aşağıdakilerle değiştirebilirsiniz:

o.doEverything();

ve daha sonra Class1 çağrısı "doThis ()" ve Class2 çağrısı "doThat ()" vb.


11
Ama bazen yapamazsın. Bir Nesneyi almanızı sağlayan bir arabirim uyguluyorsanız ve bunun hangi tür olduğunu söylemeniz gerekiyorsa, instanceof gerçekten tek seçenektir. Döküm yapmayı deneyebilirsiniz, ancak instanceof genellikle daha temizdir.
Herms

4

'instanceof' aslında + veya - gibi bir operatördür ve kendi JVM bayt kodu talimatına sahip olduğuna inanıyorum. Çok hızlı olmalı.

Bir nesnenin bazı alt sınıfların bir örneği olup olmadığını test ettiğiniz bir anahtarınız varsa, tasarımınızın yeniden işlenmesi gerekebilir. Alt sınıfa özgü davranışı alt sınıfların içine doğru itmeyi düşünün.


4

Demian ve Paul iyi bir noktaya değiniyorlar; ancak , yürütülecek kodun yerleşimi gerçekten verileri nasıl kullanmak istediğinize bağlıdır ...

Ben birçok şekilde kullanılabilecek küçük veri nesnelerinin büyük bir hayranıyım. Geçersiz kılma (polimorfik) yaklaşımını izlerseniz, nesneleriniz yalnızca "tek yönlü" kullanılabilir.

Desenler burada devreye giriyor ...

Her nesneden kendisini geçip "sizi aramasını" istemek için çift gönderme (ziyaretçi şablonunda olduğu gibi) kullanabilirsiniz - bu, nesnenin türünü çözer. Ancak (tekrar) tüm olası alt tiplerle "bir şeyler yapabilen" bir sınıfa ihtiyacınız olacaktır.

Ele almak istediğiniz her alt tip için stratejileri kaydedebileceğiniz bir strateji modeli kullanmayı tercih ederim. Aşağıdaki gibi bir şey. Bunun yalnızca tam tür eşleşmelere yardımcı olduğunu, ancak genişletilebilir olması avantajına sahip olduğunu unutmayın - üçüncü taraf katılımcılar kendi türlerini ve işleyicilerini ekleyebilir. (Bu, yeni paketlerin eklenebileceği OSGi gibi dinamik çerçeveler için iyidir)

Umarım bu başka fikirlere de ilham verir ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}

4

Jmh-java-benchmark-archetype: 2.21 tabanlı bir performans testi yazıyorum. JDK, openjdk ve sürüm 1.8.0_212'dir. Test makinesi mac pro. Test sonucu:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

Sonuç şunu gösterir: getClass, diğer testlere aykırı olan instanceOf öğesinden daha iyidir. Ancak nedenini bilmiyorum.

Test kodu aşağıdadır:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}

Tahmin edersem, instanceof'in yaptığı şey tartışmasız daha karmaşıktır. GetClass () == denetimi, 1: 1 denetimi yapar; burada instanceof, bir hiyerarşiyi kontrol eder; yani myHashSet instanceof Collection geçer, ancak myHashSet.getClass () == Collection.class olmaz. Esasen, eşdeğer işlemler değildir, bu yüzden performansın da farklı olduğuna şaşırmam.
AMTerp

3

Belirli bir JVM'nin örneğini nasıl uyguladığını söylemek zordur, ancak çoğu durumda Nesneler yapılarla ve sınıflarla karşılaştırılabilirdir ve her nesne yapısının bir örnek olduğu sınıf yapısına bir işaretçisi vardır. Yani aslında

if (o instanceof java.lang.String)

aşağıdaki C kodu kadar hızlı olabilir

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

bir JIT derleyicisinin mevcut olduğunu ve iyi bir iş yaptığını varsayarsak.

Bunun sadece bir işaretçiye eriştiğini, işaretçinin işaret ettiği belirli bir ofsette bir işaretçi aldığını ve bunu başka bir işaretçiyle karşılaştırarak (temel olarak 32 bit sayıların eşit olması testiyle aynıdır), işlemin gerçekten yapabileceğini söyleyebilirim çok hızlı ol.

Bununla birlikte, JVM'ye çok bağlı değildir. Ancak, bu kodunuzdaki darboğaz işlemi ortaya çıkarsa, JVM uygulamasını oldukça zayıf olarak değerlendiririm. JIT derleyicisine sahip olmayan ve sadece kodu yorumlayan biri bile neredeyse hiç vakit kaybetmeden bir test gerçekleştirebilmelidir.


1
Java.lang.String kaynağından miras alıp almadığını anlamak zorunda değil mi?
WW.

1
Bu yüzden "olabileceğini" söyledim. Gerçekte, önce söz konusu sınıfa karşı iAmInstanceOf'u kontrol eden bir döngü gerçekleştirir, daha sonra o kalıtım ağacından yukarı doğru gider ve her süper sınıf o için bu kontrolü tekrarlar (bu döngüyü birkaç kez çalıştırması gerekebilir) maç için)
Mecki

3

Performans anında size geri döneceğim. Ancak problemden (veya eksikliğinden) kaçınmanın bir yolu, üzerinde gerçekleştirmeniz gereken tüm alt sınıflara bir üst arayüz oluşturmak olacaktır. Arayüz, alt sınıflardaki kontrol örneklerini gerçekleştirmeniz gereken tüm yöntemlerin süper bir seti olacaktır . Bir yöntem belirli bir alt sınıfa uygulanmadığında, bu yöntemin kukla bir uygulamasını sağlayın. Sorunu yanlış anlamadıysam, geçmişte problemi bu şekilde çözdüm.


2

InstanceOf , Nesne Tabanlı tasarımın zayıf olduğuna dair bir uyarıdır.

Güncel JVMs demek InstanceOf başlı başına bir performans endişe çok değil. Kendinizi özellikle çekirdek işlevsellik için çok fazla kullanıyorsanız, muhtemelen tasarıma bakmanın zamanı gelmiştir. Daha iyi bir tasarıma yeniden düzenleme performans (ve basitlik / sürdürülebilirlik) kazanımları, gerçek örnekte harcanan gerçek işlemci döngülerinden çok daha ağır basacaktır. çağrısında .

Çok küçük basit bir programlama örneği vermek.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

Zayıf bir mimari mi, daha iyi bir seçim, her çocuk sınıfının bir yöntemi (doSomething) geçersiz kıldığı iki çocuk sınıfının üst sınıfı olması için daha iyi bir seçim olurdu, böylece kod şöyle görünecektir:

Someobject.doSomething();

61
Farkındayım. Sorduğum şey bu değildi.
Josh

Bu iyi bir nokta olarak oy vermek ya da değil emin değilim, ama sorulan soruya cevap vermez ...
jklp

7
Kod örneği aslında çok kötü olduğunu düşünüyorum: Double sınıf genişletemezsiniz ve ayrıca başka bir sınıftan Double türetemezsiniz. Örnek için başka sınıflar kullansaydınız, sorun olmazdı.
Lena Schimmel

6
Ayrıca, SomeObject öğesinin alt sınıfları değer nesneleriyse, mantığı bunlara koymak istemezsiniz. Örneğin, Pie ve Roast putInOven () ve putInMouth () mantığı için doğru yer olmayabilir.
sk.

kendini pişirme pasta ve rosto olsa harika olurdu
binboavetonik

2

Modern Java sürümünde instanceof operatörü basit bir yöntem çağrısı olarak daha hızlıdır. Bunun anlamı:

if(a instanceof AnyObject){
}

daha hızlı:

if(a.getType() == XYZ){
}

Başka bir şey, birçok örneği basamaklandırmanız gerekiyorsa. Daha sonra getType () yöntemini yalnızca bir kez çağıran bir anahtar daha hızlıdır.


1

Eğer hız sizin tek amacınızsa, alt sınıfları tanımlamak için int sabitlerini kullanmak milisaniyede tıraş oluyor gibi görünmektedir

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

korkunç bir OO tasarımı, ancak performans analiziniz burası darboğazın nerede olduğunu gösteriyorsa belki de. Kodumda, gönderim kodu toplam yürütme süresinin% 10'unu alır ve bu toplam hızın% 1'ine katkıda bulunabilir.


0

Projenizde gerçekten bir performans sorunu olup olmadığını ölçmeniz / profil yapmanız gerekir. Eğer öyleyse, yeniden tasarlamayı tavsiye ederim - mümkünse. Platformun yerel uygulamasını (C ile yazılmış) yenemeyeceğinizden eminim. Bu durumda çoklu kalıtım da düşünülmelidir.

Sorun hakkında daha fazla bilgi vermelisiniz, örneğin yalnızca somut türlerle ilgileniyorsanız, örneğin bir Harita <Sınıfı, Nesne> gibi ilişkilendirilebilir bir mağaza kullanabilirsiniz.


0

Peter Lawrey'in son sınıflar için örneğe ihtiyacınız olmadığına ve sadece referans eşitliğini kullanabileceğinize dikkat edin, dikkatli olun! Final sınıfları genişletilemese de, aynı sınıf yükleyici tarafından yüklenmeleri garanti edilmez. X.getClass () == SomeFinal.class veya onun ilkini, kodun bu bölümü için oyunda yalnızca bir sınıf yükleyici olduğundan kesinlikle eminseniz kullanın.


4
Bir sınıf farklı bir sınıf yükleyici tarafından yüklenirse, instanceof'in de eşleşeceğini sanmıyorum.
Peter Lawrey

0

Bir enum yaklaşımını da tercih ederim, ancak alt sınıfları getType()yöntemi uygulamaya zorlamak için soyut bir temel sınıf kullanırdım .

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}

0

Bu sayfadaki genel fikir birliğine “instanceof” in endişelenecek kadar pahalı olmadığını bir karşı örnek göndermeye değeceğini düşündüm. Ben (bazı optimizasyon tarihi girişimi) bir iç döngüde bazı kod vardı bulundu

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

burada bir SingleItem üzerinde head () çağrısı değişmeden değeri döndürür. Kodu değiştirme

seq = seq.head();

döngüde iki katına dönüşüm gibi bazı oldukça ağır şeyler olmasına rağmen, bana 269ms'den 169ms'ye bir hız kazandırıyor. Elbette hızlandırmanın, koşullu dalın ortadan kaldırılmasından, operatörün kendisinin örneğini ortadan kaldırmaktan daha fazla olması mümkündür; ama bahsetmeye değer olduğunu düşündüm.


Bu ifkendisi yüzünden olabilir . trueS ve falses'nin dağılımı eşitse, spekülatif uygulama işe yaramaz hale gelir ve bu da önemli gecikmelere yol açar.
Dmytro

-4

Yanlış şeye odaklanıyorsunuz. İnstanceof ile aynı şeyi kontrol etmek için kullanılan diğer herhangi bir yöntem arasındaki fark muhtemelen ölçülemez. Performans kritikse Java muhtemelen yanlış dildir. Bunun en büyük nedeni, VM'nin çöp toplamaya gitmek istediğine karar verdiğinde kontrol edememenizdir, bu da CPU'yu büyük bir programda birkaç saniye boyunca% 100'e çıkarabilir (MagicDraw 10 bunun için harikaydı). Her bilgisayarın kontrolü sizde değilse, bu program hangi JVM'nin hangi sürümde olacağını garanti edemez ve eskilerinin çoğunda büyük hız sorunları vardı. Küçük bir uygulama varsa size Java ile Tamam olabilir, ama sürekli okuma ve veri atma, sonra eğer olacak zaman GC başladı fark.


7
Bu, daha modern java çöp toplama algoritmaları için eskisinden çok daha az doğrudur. En basit algoritmalar bile, kullandıktan hemen sonra ne kadar bellek harcadığınızı umursamaz - sadece genç nesil koleksiyonlarda ne kadar bellek tutulduğunu umursarlar.
Bill Michell

3
Harika, ancak en son JVM'deyim ve GC çalıştığında bilgisayarım hala tarar. Çift çekirdekli, 3GB ram sunucusunda. Java, performans gerçekten önemliyse kullanılacak bir dil değildir.
0'da tloach

@David: Uygulamanız bir süre için kullanımdan kalktığında sorun yaşamak için gerçek zamanlıya ihtiyacınız yok. Karşılaştığım eğlenceli bir şey, GC çalıştığında ölen bir TCP akışına bağlanan, önce akışı kapatmaması ve geri döndüğünde ağ trafiğinin aşırı yüklenmesini kaldıramadığı için bir java uygulamasıdır - hemen GC'nin çalıştığı bir döngüye gidin, uygulama devam ettiğinde, bir sürü veriyi çalkalamaya çalışır, bu da hafızanın tükenmesine neden olur, bu da GC'yi tetikler, vb. Java birçok görev için mükemmeldir, ancak çok güçlü performans bir gerekliliktir.
13'te tloach

6
@tloach bana kötü uygulama tasarımı gibi geliyor. "performans" hakkında sanki tek boyutluymuş gibi konuşuyorsunuz. Örneğin, çok büyük veri kümelerinin hızlı etkileşimli istatistiksel analizini ve görselleştirmesini sağlamada veya çok büyük işlem hacimlerini çok hızlı bir şekilde işlemede performans gösteren birçok java uygulamasıyla (ve üzerinde) çalıştım. "performans" sadece bir şey değildir ve birinin hafızayı kötü yöneten ve GC'nin kendi yoluna girmesine izin veren bir uygulama yazabilmesi, "performans" gerektiren herhangi bir şeyin başka bir şeye yazılması gerektiği anlamına gelmez.
David Moles
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.