it-swarm-eu.dev

Vliv použití instanceof v Javě

Pracuji na aplikaci a jeden designový přístup zahrnuje extrémně těžké použití operátoru instanceof. I když vím, že OO design obecně se snaží vyhnout použití instanceof, to je jiný příběh a tato otázka je čistě související s výkonem. Zajímalo by mě, jestli existuje nějaký dopad na výkon? Je to stejně rychlé jako ==?

Například mám základní třídu s 10 podtřídami. V jedné funkci, která bere základní třídu, kontroluji, zda je třída instancí podtřídy a provádí nějakou rutinu. 

Jedním z dalších způsobů, jak jsem myslel na řešení, bylo místo toho použít primitivní číslo typu "id id" a použít bitovou masku k reprezentaci kategorií podtříd, a pak jen udělat bitovou masku porovnání podtříd "id id" na a. konstantní maska ​​představující kategorii.

Je instanceof nějak optimalizován JVM, aby byl rychlejší než to? Chci se držet Java, ale výkon aplikace je kritický. Bylo by skvělé, kdyby někdo, kdo byl na této cestě dřív, mohl nabídnout radu. Jsem nitpicking příliš mnoho nebo se zaměřením na špatnou věc optimalizovat?

282
Josh

Moderní kompilátory JVM/JIC odstranily výkonový zásah většiny tradičně „pomalých“ operací, včetně instanceof, zpracování výjimek, reflexe atd.

Jak napsal Donald Knuth: „Měli bychom zapomenout na malou efektivitu, řekněme na 97% času: předčasná optimalizace je kořenem všeho zla.“ Výkon instanceof pravděpodobně nebude problém, takže neztrácejte čas přijetím s exotickými řešeními, dokud si nejste jisti, že je to problém.

246
Steve

Přístup

Napsal jsem srovnávací program k vyhodnocení různých implementací:

  1. instanceof implementace (jako reference) 
  2. objekt orientovaný pomocí abstraktní třídy a @Override testovací metody
  3. implementace vlastního typu
  4. getClass() == _.class implementace

Použil jsem jmh ke spuštění benchmark s 100 rozcvičkami, 1000 iterací pod měřením a 10 vidličkami. Takže každá možnost byla měřena s 10 000 krát, což trvá 12:18:57 spustit celý benchmark na mém MacBook Pro s MacOS 10.12.4 a Java 1.8. Benchmark měří průměrný čas každé varianty. Pro více informací viz moje implementace na GitHub

Pro úplnost: Existuje předchozí verze této odpovědi a můj benchmark

Výsledek

 | Provoz | Doba provozu v nanosekundách na operaci Relativní vzhledem k instanci | 
 | ------------ | --------------------------- ----------- | ------------------------ | 
 | INSTANCEOF | 39 598 ± 0,022 ns/op | 100,00% | 
 | GETCLASS | 39,687 ± 0,021 ns/op | 100,22% | 
 | TYP | 46,295 ± 0,026 ns/op | 116,91% | 
 | OO | 48,078 ± 0,026 ns/op | 121,42% | 

tl; dr

V jazyce Java 1.8 je instanceof nejrychlejší přístup, i když getClass() je velmi blízko. 

230
Michael Dorner

Prostě jsem provedl jednoduchý test, abych zjistil, jak je výkon instanceOff porovnán s jednoduchým voláním s.equals () na objekt řetězce pouze jedním písmenem.

v 10.000.000 smyčce instanceOf mi dal 63-96ms, a řetězec se rovná mi 106-230ms

Použil jsem Java jvm 6.

Takže v mém jednoduchém testu je rychlejší udělat instanci místo porovnání jednoho znakového řetězce.

použití .equals () Integer namísto řetězec mi dal stejný výsledek, jen když jsem použil == i byl rychlejší než instanceOf o 20ms (ve smyčce 10.000.000)

72
Dan Mihai Ile

Položky, které budou určovat dopad na výkon, jsou:

  1. Počet možných tříd, pro které mohl operátor instanceof vrátit hodnotu true
  2. Distribuce vašich dat - je většina operací instance vyřešena v prvním nebo druhém pokusu? Budete chtít dát své nejpravděpodobnější operace nejdříve.
  3. Prostředí nasazení. Běh na Sun Solaris VM se výrazně liší od Windows JVM. Systém Solaris bude ve výchozím nastavení spuštěn v režimu „server“, zatímco systém Windows bude spuštěn v režimu klienta. Optimalizace JIT v systému Solaris umožní přístup ke všem metodám. 

Vytvořil jsem microbenchmark pro čtyři různé metody expedice . Výsledky systému Solaris jsou následující, přičemž menší počet je rychlejší:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
17
brianegge

Odpovězte na svou poslední otázku: Pokud vám profiler neřekne, že v instanci trávíte směšné množství času: Ano, jste nitpicking.

Než přemýšlíme o optimalizaci něčeho, co nikdy nebylo nutné optimalizovat: Zapište si algoritmus nejsnadnějším způsobem a spusťte jej. Spusťte jej, dokud se jit-kompilátor nedostane šanci ho optimalizovat sám. Máte-li pak s tímto kódem problémy, použijte profiler, který vám řekne, kde získat co nejvíce a optimalizovat to.

V dobách vysoce optimalizujících překladačů budou vaše odhady ohledně úzkých míst pravděpodobně zcela chybné.

A v pravém duchu této odpovědi (kterou jsem naprosto věří): absolutně nevím, jak se instanceof a == týkají, jakmile jit-compiler dostal šanci optimalizovat to.

Zapomněl jsem: Nikdy neměřte první běh.

16
Olaf Kock

Mám stejnou otázku, ale protože jsem nenašel 'metriky výkonu' pro případ použití podobný mému, udělal jsem další ukázkový kód. Na mém hardwaru a Java 6 & 7 je rozdíl mezi instancí a přepnutím na 10mln iterací

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

Takže instanceof je opravdu pomalejší, zejména na obrovském počtu příkazů if-else-if, nicméně rozdíl bude v reálné aplikaci zanedbatelný.

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()));
    }
}
12
Xtra Coder

instanceof je opravdu rychlá, trvá jen několik instrukcí CPU.

Je zřejmé, že pokud třída X nemá načtené podtřídy (JVM ví), instanceof lze optimalizovat jako:

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

Hlavní náklady jsou jen čtení!

Pokud X má načtené podtřídy, je potřeba několik dalších čtení; oni jsou pravděpodobně co-umístil tak zvláštní náklady jsou také velmi nízké.

Dobré zprávy všichni!

8
irreputable

instanceof bude pravděpodobně dražší než jednoduché rovnice ve většině reálných implementací světa (to znamená ty, kde je instanceof skutečně potřeba, a nemůžete to řešit pouze přepsáním běžné metody, jako je každá učebnice začátečníků, stejně jako Demian výše naznačují).

Proč je to? Protože to, co se pravděpodobně stane, je, že máte několik rozhraní, která poskytují některé funkce (řekněme, rozhraní x, y a z) a některé objekty, které mohou manipulovat, které mohou (nebo nemusí) implementovat jedno z těchto rozhraní ... ale ne přímo. Řekněte například:

w rozšiřuje x

A realizuje w

B rozšiřuje A

C rozšiřuje B, realizuje y

D rozšiřuje C, implementuje z

Předpokládám, že zpracovávám instanci D, objekt d. Výpočet (d instanceof x) vyžaduje, aby se d.getClass (), smyčka přes rozhraní, které implementuje vědět, zda jeden je == až x, a pokud ne tak znovu rekurzivně pro všechny své předky ... V našem případě, pokud provedete první průzkum tohoto stromu, přinese přinejmenším 8 srovnání, předpokládáme, že y a z nic nerozšíří ...

Komplexnost derivačního stromu reálného světa bude pravděpodobně vyšší. V některých případech může JIT optimalizovat většinu z nich, je-li schopen ve všech možných případech předem vyřešit situaci, která se rozšiřuje x. Realisticky však budete procházet přes tento strom po většinu času.

Pokud se to stane problémem, navrhl bych místo toho použít mapu handleru, spojující konkrétní třídu objektu s uzávěrem, který provádí manipulaci. Odstraní fázi přechodu stromu ve prospěch přímého mapování. Nicméně, dejte si pozor, pokud jste nastavili handler pro C.class, můj objekt d výše nebude rozpoznán.

zde jsou moje 2 centy, doufám, že pomohou ...

5
Varkhan

instanceof je velmi efektivní, takže je nepravděpodobné, že by váš výkon utrpěl. Nicméně použití mnoha instancí naznačuje problém návrhu.

Pokud můžete použít xClass == String.class, je to rychlejší. Poznámka: pro konečné třídy nepotřebujete instanci.

4
Peter Lawrey

Demian a Paul zmiňují dobrý bod; nicméně , umístění kódu, který se má provést, závisí na tom, jak chcete data používat ...

Jsem velkým fanouškem malých datových objektů, které lze použít mnoha způsoby. Pokud budete postupovat podle přepisu (polymorfního), vaše objekty mohou být použity pouze "jedním směrem".

Zde přicházejí vzory ...

Můžete použít double-dispatch (jako ve vzorci návštěvníků), abyste požádali každý objekt, aby "zavolal" předávání sám - to vyřeší typ objektu. Nicméně (opět) budete potřebovat třídu, která může "dělat věci" se všemi možnými podtypy.

Dávám přednost použití strategického vzoru, kde můžete zaregistrovat strategie pro každý podtyp, se kterým chcete pracovat. Něco jako následující. Všimněte si, že to pomáhá pouze pro přesný typ shody, ale má tu výhodu, že je rozšiřitelná - přispěvatelé třetích stran mohou přidat své vlastní typy a obslužné rutiny. (To je dobré pro dynamické rámce, jako je OSGi, kde mohou být přidány nové svazky)

Doufejme, že to bude inspirovat některé další nápady ...

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

'instanceof' je vlastně operátor, jako + nebo -, a věřím, že má vlastní instrukci JVM bytecode. Mělo by to být hodně rychlé.

Neměl bych, že pokud máte přepínač, ve kterém testujete, zda je objekt instancí nějaké podtřídy, je třeba, aby byl váš návrh přepracován. Zvažte posunutí chování specifického podtřídy do podtříd samotných.

4

Instanceof je velmi rychlá. Snižuje se na bytecode, který se používá pro porovnání referencí třídy. Vyzkoušejte několik milionů instancí ve smyčce a přesvědčte se sami.

4
Apocalisp

Je těžké říci, jak určitý JVM implementuje instanci, ale ve většině případů jsou objekty srovnatelné se strukturami a třídami stejně a každý objekt struct má ukazatel na strukturu třídy, kterou je instancí. Takže vlastně instanceof pro

if (o instanceof Java.lang.String)

může být stejně rychlý jako následující kód C

if (objectStruct->iAmInstanceOf == &Java_lang_String_class)

za předpokladu, že JIT kompilátor je na místě a dělá slušnou práci.

Vzhledem k tomu, že se jedná pouze o přístup k ukazateli, získání ukazatele na určité odsazení ukazatel ukazuje na a porovnání s jiným ukazatelem (který je v podstatě stejný jako testování na 32 bitových číslech, které jsou stejné), řekl bych, že operace může skutečně být velmi rychlý.

To nemusí, ale záleží hodně na JVM. Pokud by se však ukázalo, že se jedná o překážku ve vašem kódu, považuji implementaci protokolu JVM za velmi špatnou. Dokonce i ten, který nemá kompilátor JIT a pouze kód interpretů, by měl být schopen provést test instanceof prakticky bez času.

3
Mecki

InstanceOf je varování špatného návrhu orientovaného na objekt.

Aktuální JVMs znamenají, že instanceOf není moc výkonné starosti samy o sobě. Pokud zjistíte, že používáte to hodně, zejména pro základní funkce, je pravděpodobně čas podívat se na design. Výkon (a jednoduchost/udržovatelnost) zisky z refactoringu do lepšího designu výrazně převáží všechny skutečné cykly procesorů, které jsou vynaloženy na skutečné instanceOf volání.

Dát velmi malý zjednodušující programovací příklad.

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

Je špatná architektura lepší volbou by bylo mít SomeObject mateřskou třídu dvou podřízených tříd, kde každá podřízená třída přepíše metodu (doSomething), aby kód vypadal takto:

Someobject.doSomething();
3
Demian Krige

Vrátím se k vám na představení. Ale způsob, jak se vyhnout problému (nebo jeho nedostatku), by bylo vytvořit rodičovské rozhraní pro všechny podtřídy, na kterých potřebujete udělat instanci. Rozhraní bude super sada all metod v sub-třídách, pro které musíte provést instanci check. Pokud se metoda nevztahuje na určitou podtřídu, stačí provést fiktivní provedení této metody. Pokud jsem problém nepochopil, tak jsem v minulosti problém vyřešil. 

3
Jose Quijada

Obecně je důvodem, proč je operátor "instanceof" v takovém případě (kde instanceof kontroluje podtřídy této základní třídy), důvod, proč je to, co byste měli dělat, přesunutí operací do metody a jejich přepsání pro příslušné podtříd. Pokud máte například:

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

To můžete nahradit

o.doEverything();

a potom mají implementaci "doEverything ()" v volání Class1 "doThis ()" a volání Class2 "doThat ()" a tak dále.

2
Paul Tomblin

V moderní verzi Java je operátor instanceof rychlejší jako volání jednoduchou metodou. To znamená:

if(a instanceof AnyObject){
}

je rychlejší jako:

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

Další věc je, pokud potřebujete kaskádovitě mnoho instancí. Pak je přepínač, který volá pouze jednou, když je getType () rychlejší.

2
Horcrux7

Je-li rychlost vaším jediným cílem, pak použití int konstanty k identifikaci podtřídy se zdá, že oholí milisekundy času

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;
}

hrozné OO design, ale pokud vaše analýza výkonu ukáže, že je to místo, kde jste zúžení je pak možná. V mém kódu trvá odesílací kód 10% celkové doby provedení a to může přispět k celkovému zlepšení rychlosti o 1%.

1
Salix alba

Také dávám přednost přístupu enum, ale já bych použil abstraktní základní třídu k vynucení podtříd implementovat metodu getType().

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
mike

Myslel jsem, že by mohlo být vhodné podat protiklad k obecnému konsensu na této stránce, že "instanceof" není dost drahý na to, aby se obával. Zjistil jsem, že jsem měl nějaký kód ve vnitřní smyčce, která (v nějakém historickém pokusu o optimalizaci) udělala

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

kde volání head () na SingleItem vrátí hodnotu beze změny. Nahrazení kódu kódem

seq = seq.head();

dává mi zrychlení z 269ms na 169ms, a to navzdory skutečnosti, že ve smyčce se dějí nějaké velmi těžké věci, jako je řetězová konverze. Je samozřejmě možné, že zrychlení je více způsobeno odstraněním podmíněné větve než odstraněním samotného operátora instanceof; ale myslel jsem, že to stojí za zmínku. 

0
Michael Kay

S ohledem na poznámku Petera Lawreye, že nepotřebujete instanci pro poslední třídy a můžete použít pouze referenční rovnost, buďte opatrní! Přestože konečné třídy nelze rozšířit, není zaručeno, že budou načteny stejným třídičem. Použijte pouze x.getClass () == SomeFinal.class nebo jeho ilk, pokud jste naprosto pozitivní, že pro tuto sekci kódu je ve hře pouze jeden classloader.

0
Miles Elam

Měli byste měřit/profil, pokud je to ve skutečnosti problém ve vašem projektu. Pokud je to možné, doporučuji redesign - pokud je to možné. Jsem si jistý, že nemůžete porazit nativní implementaci platformy (napsanou v jazyce C). Měli byste také zvážit vícenásobné dědictví v tomto případě.

Měli byste říct více o problému, možná byste mohli použít asociativní úložiště, např. a Map <Class, Object> pokud se zajímáte pouze o konkrétní typy.

0
Karl