Pergunta

Eu estou trabalhando em um aplicativo e uma abordagem de projeto envolve uso extremamente pesado do operador instanceof. Enquanto eu sei que o design OO geralmente tenta evitar o uso instanceof, que é uma história diferente e esta questão é puramente relacionado ao desempenho. Eu queria saber se existe algum impacto no desempenho? Será que é tão rápido quanto ==?

Por exemplo, eu tenho uma classe base com 10 subclasses. Em uma única função que leva a classe base, eu faço verifica se a classe é uma instância da subclasse e realizar alguma rotina.

Uma das outras maneiras que eu pensei de resolvê-lo era usar um "id tipo" inteiro primitivo vez, e usar uma máscara de bits para representar categorias das subclasses, e depois é só fazer uma comparação máscara bit das subclasses "tipo id "a uma máscara constante que representa a categoria.

É instanceof alguma forma otimizada pela JVM para ser mais rápido do que isso? Eu quero ficar com Java, mas o desempenho do aplicativo é crítico. Seria legal se alguém que tenha sido por esta via antes poderia oferecer alguns conselhos. Am I picuinhas demais ou incidindo sobre a coisa errada a otimizar?

Foi útil?

Solução

compiladores Modern JVM / JIC ter removido o sucesso da maioria das operações tradicionalmente "lentos", incluindo instanceof, tratamento de exceções, reflexão, etc desempenho.

Como Donald Knuth escreveu: "Devemos esquecer sobre pequenas eficiências, digamos cerca de 97% do tempo: otimização prematura é a raiz de todo o mal." O desempenho do instanceof provavelmente não será um problema, então não perca seu tempo chegando com soluções exóticas até ter certeza de que é o problema.

Outras dicas

Abordagem

Eu escrevi um programa de benchmark para avaliar diferentes implementações:

  1. implementação instanceof (como referência)
  2. objeto orientado através de uma classe abstrata e @Override um método de teste
  3. usando uma implementação tipo próprio
  4. implementação getClass() == _.class

jmh para executar o benchmark com 100 chamadas de aquecimento, 1000 iterações sob medida, e com 10 garfos. Assim, cada opção foi medida com 10 000 vezes, o que leva 12:18:57 para executar toda a referência no meu MacBook Pro com o MacOS 10.12.4 e Java 1.8. O benchmark mede o tempo médio de cada opção. Para mais detalhes veja minha implementação no GitHub .

Por uma questão de exaustividade: Há um versão anterior desta resposta e minha referência .

Resultados

| Operation  | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op                 | 100,00 %               |
| GETCLASS   | 39,687 ± 0,021 ns/op                 | 100,22 %               |
| TYPE       | 46,295 ± 0,026 ns/op                 | 116,91 %               |
| OO         | 48,078 ± 0,026 ns/op                 | 121,42 %               |

tl; dr

Em Java 1.8 instanceof é a abordagem mais rápida, embora getClass() está muito próximo.

Eu só fiz um teste simples para ver como o desempenho InstanceOf está comparando a uma simples s.equals () chamada para um objeto string com apenas uma letra.

em um loop 10.000.000 a InstanceOf me deu 63-96ms, e os iguais de cordas deu-me 106-230ms

Eu costumava java jvm 6.

Então, no meu teste simples é mais rápido para fazer um instanceof em vez de uma comparação de string um caractere.

usando .equals do Integer () em vez de corda de me deram o mesmo resultado, só quando eu usei o == i foi mais rápido do que InstanceOf por 20ms (em um loop 10.000.000)

Os itens que irão determinar o impacto no desempenho são:

  1. O número de possíveis classes para as quais o operador instanceof poderia retornar true
  2. A distribuição de seus dados - são a maioria das operações instanceof resolvido na primeira ou segunda tentativa? Você vai querer colocar o seu mais provável para retornar verdadeiras operações em primeiro lugar.
  3. O ambiente de implantação. Rodando em um Sun Solaris VM é significativamente diferente do que o Windows JVM do Sol. Solaris será executado no modo 'servidor' por padrão, enquanto o Windows será executado em modo cliente. As otimizações JIT no Solaris, fará com que todo o acesso método capaz o mesmo.

Eu criei um microbenchmark por quatro métodos diferentes de envio . Os resultados do Solaris são as seguintes, com o menor número de ser mais rápido:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

Respondendo a sua última pergunta: A menos que um profiler lhe diz, que você gasta quantias ridículas de tempo em um instanceof:. Sim, você está picuinhas

Antes de se perguntando sobre como otimizar algo que nunca precisou ser otimizado: Escreva seu algoritmo da forma mais legível e executá-lo. Executá-lo, até que o JIT-compiler tem a chance de otimizá-lo em si. Se você, em seguida, ter problemas com este pedaço de código, use um profiler para lhe dizer, onde a ganhar o máximo e otimizar isso.

Em tempos de compiladores altamente otimizados, seus palpites sobre os gargalos serão susceptíveis de ser completamente errado.

E no verdadeiro espírito desta resposta (que eu wholeheartly acreditar):. Eu absolutamente não sei como instanceof e == relacionar uma vez que o JIT-compiler teve a chance de otimizá-lo

Eu esqueci: Nunca medir a primeira execução

.

Eu tenho a mesma pergunta, mas porque eu não encontrar 'métricas de desempenho' para caso de uso semelhante ao meu, eu fiz algum código mais amostra. No meu hardware e Java 6 e 7, a diferença entre instanceof e ligar 10mln iterações é

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

Assim, instanceof é realmente mais lento, especialmente no número enorme de if-else-if, no entanto diferença será insignificante dentro de aplicação real.

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

instanceof é muito rápido, levando apenas algumas instruções da CPU.

Aparentemente, se uma X classe não tem subclasses carregados (JVM sabe), instanceof pode ser otimizado como:

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

O custo principal é apenas uma leitura!

Se X tem subclasses carregados, um pouco mais lê são necessários; que são susceptíveis de co-localizados de modo que o custo extra é muito baixo também.

Boas notícias a todos!

instanceof é provavelmente vai ser mais caro do que um simples é igual na maioria das implementações do mundo real (isto é, aquelas onde instanceof é realmente necessário, e você não pode simplesmente resolvê-lo, substituindo um método comum, como qualquer livro iniciante bem como Demian acima sugerem).

Por que isso? Porque o que é provavelmente vai acontecer é que você tem várias interfaces, que fornecem algumas funcionalidades (digamos, interfaces de x, y e z), e alguns objetos para manipular que pode (ou não) implementar uma dessas interfaces de ... mas Não diretamente. Digamos, por exemplo, eu tenho:

w estende x

implementa um w

B estende-A

C estende-B, implementos y

D estende-se C, implementos z

Suponha estou processar uma instância de D, o objeto d. Computação (d instanceof x) requer a tomar d.getClass (), percorrer as interfaces implementa saber se um é == para x, e se não fazê-lo novamente de forma recursiva para todos os seus antepassados ??... No nosso caso, se você fizer uma primeira exploração amplitude dessa árvore, os rendimentos de pelo menos 8 comparações, supondo Y e Z não se estendem nada ...

A complexidade de uma árvore de derivação do mundo real é provável que seja mais elevado. Em alguns casos, o JIT pode otimizar a maioria de fora, se é capaz de resolver com antecedência d como sendo, em todos os casos possíveis, um exemplo de algo que se estende x. Realisticamente, porém, você vai passar por essa passagem de árvore a maior parte do tempo.

Se isso se torna um problema, gostaria de sugerir usando um mapa manipulador vez, ligando a classe concreta do objeto a um fecho que faz o manuseio. Ele remove a fase de passagem de árvore em favor de um mapeamento direto. No entanto, cuidado que se você tiver definido um manipulador para C.class, meu objeto d acima não será reconhecido.

aqui estão meus 2 centavos, espero que ajude ...

'instanceof' é na verdade um operador, como + ou -, e acredito que ele tem sua própria instrução JVM bytecode. Deve ser muito rápido.

Eu não deveria que se você tiver um interruptor de onde você está testando se um objeto é uma instância de alguma subsclass, em seguida, seu projeto pode precisar ser reformulado. Considere empurrando o comportamento específico de subclasse para dentro das próprias subclasses.

instanceof é muito rápido. Ela resume-se a um bytecode que é utilizado para a comparação de referência de classe. Tente alguns milhões de instanceofs em um loop e veja por si mesmo.

Demian e Paul mencionar um bom ponto; no entanto , a colocação do código para executar realmente depende de como você deseja usar os dados ...

Eu sou um grande fã de pequenos objetos de dados que podem ser usados ??de muitas maneiras. Se você seguir a abordagem override (polimórfica), seus objetos só pode ser usado "one-way".

Este é onde os padrões vêm em ...

Você pode usar double-expedição (como no padrão do visitante) para pedir a cada objeto a "chamar-lhe" passar em si - o que irá resolver o tipo do objeto. No entanto (novamente) você vai precisar de uma classe que pode "fazer coisas" com todos os possíveis subtipos.

Eu prefiro usar um padrão de estratégia, onde você pode registrar estratégias para cada subtipo você deseja manipular. Algo como o seguinte. Note que isto só ajuda para partidas tipo exato, mas tem a vantagem de que é extensível - contribuintes de terceiros podem adicionar seus próprios tipos e manipuladores. (Isso é bom para os quadros dinâmicos, como OSGi, onde novos pacotes podem ser adicionados)

Esperemos que isto irá inspirar algumas outras idéias ...

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

instanceof é muito eficiente, para que o seu desempenho é improvável que sofrer. No entanto, usando lotes de instanceof sugere um problema de design.

Se você pode usar XCLASS == String.class, este é mais rápido. Nota: você não precisa instanceof para as classes finais

.

É difícil dizer como um certo implementos JVM instância, mas na maioria dos casos, os objetos são comparáveis ??a estruturas e classes estão bem e cada objeto struct tem um ponteiro para a estrutura da classe é uma instância. Então, na verdade instanceof para

if (o instanceof java.lang.String)

pode ser tão rápido quanto o seguinte código C

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

assumindo um compilador JIT está no lugar e faz um trabalho decente.

Considerando que este é apenas acessar um ponteiro, obtendo um ponteiro em um determinado deslocamento o ponteiro aponta para e comparando este a outro ponteiro (que é basicamente o mesmo que testar para 32 números de bits são iguais), eu diria que a operação pode realmente ser muito rápido.

Ele não tem que, no entanto, depende muito do JVM. No entanto, se este viria a ser a operação de estrangulamento em seu código, eu consideraria a implementação JVM bastante pobre. Mesmo aquele que não tem compilador JIT e só interpreta código deve ser capaz de fazer um teste de instanceof em praticamente nenhum momento.

InstanceOf é um aviso de má design orientado a objeto.

JVMs atuais fazer significar a InstanceOf não é muito de uma preocupação desempenho em si mesmo. Se você está encontrando-se a usá-lo muito, especialmente para a funcionalidade do núcleo, provavelmente é hora de olhar para o design. O desempenho (e simplicidade / maintainability) ganhos de refatoração para um melhor design muito compensarão quaisquer ciclos de processador reais gastos na real InstanceOf chamada.

Para dar um pequeno exemplo de programação simplista.

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

É uma arquitetura pobre uma escolha melhor teria sido ter SomeObject ser a classe pai de duas classes de crianças, onde cada classe criança substitui um método (doSomething) para que o código ficaria assim:

Someobject.doSomething();

Eu vou voltar para você sobre o desempenho instanceof. Mas uma maneira de evitar o problema (ou falta dela) completamente seria a criação de uma interface de pai para todos os subclasses em que você precisa fazer instanceof. A interface será um conjunto de super todas os métodos em sub-classes para o qual você precisa fazer instanceof cheque. Quando um método não se aplica a uma sub-classe específica, basta fornecer uma implementação fictícia deste método. Se eu não entenda mal o problema, isto é como eu comecei em torno do problema no passado.

Geralmente a razão pela qual a "instanceof" operador é desaprovado em um caso como esse (onde o instanceof está verificando subclasses desta classe base) é porque o que você deve fazer é mover as operações em um método e overridding-lo para as subclasses apropriadas. Por exemplo, se você tem:

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

Você pode substituir isso com

o.doEverything();

e, então, a implementação de "doEverything ()" em Class1 chamada "doThis ()" e, em chamada Class2 "doThat ()", e assim por diante.

Na versão moderna Java o operador instanceof é mais rápido como uma chamada de método simples. Isso significa que:

if(a instanceof AnyObject){
}

é mais rápido como:

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

Outra coisa é se você precisa cascata muitos instanceof. Em seguida, um interruptor que apenas chamar uma vez getType () é mais rápido.

Se a velocidade é o seu único objetivo, em seguida, usando constantes int para identificar subclasses parece barbear milissegundos do tempo

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

terrível projeto OO, mas se a sua análise de desempenho indica que este é o lugar onde você gargalo é então talvez. No meu código o código expedição leva 10% do tempo total de execução e este talvez contribuiu para uma melhoria de velocidade total de 1%.

Eu escrevo um teste de desempenho baseado em jmh-java-referência-arquétipo: 2,21. JDK é openjdk e versão é 1.8.0_212. A máquina de teste é Mac Pro. resultado do teste é:

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

Os resultado mostra que: getClass é melhor do que InstanceOf, o que é contrário com outro teste. No entanto, eu não sei porquê.

O código de teste está abaixo:

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

Você deve medir / perfil se é realmente um problema de desempenho em seu projeto. Se for eu recomendo um redesenho - se possível. Tenho certeza de que você não pode bater implementação nativa da plataforma (escrito em C). Você também deve considerar a herança múltipla neste caso.

Você deve dizer mais sobre o problema, talvez você poderia usar um armazenamento associativo, por exemplo, um Map Se você está interessado apenas nos tipos de concreto.

Com relação à nota de Pedro Lawrey que você não precisa instanceof para as classes finais e pode apenas usar uma igualdade de referência, tenha cuidado! Mesmo que as classes final não pode ser estendida, eles não são garantidos para ser carregado pelo mesmo carregador de classe. Apenas usar x.getClass () == SomeFinal.class ou sua laia se você está absolutamente positiva que há apenas um carregador de classe em jogo para essa seção de código.

Eu também prefiro uma abordagem enum, mas eu gostaria de usar uma classe base abstrata para forçar as subclasses para implementar o método 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;
    }
  }
}

Eu pensei que poderia valer a pena submeter um contra-exemplo para o consenso geral sobre esta página que "instanceof" não é caro o suficiente para se preocupar. Eu descobri que eu tinha algum código em um loop interno que (em uma tentativa histórica no optimization) fez

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

onde chamando cabeça () em um SingleItem retorna o valor inalterado. Substituir o código por

seq = seq.head();

me dá uma velocidade-up de 269ms para 169ms, apesar do fato de que existem algumas coisas bem pesadas acontecendo no circuito, como conversão de cadeia para duplo. É possível, é claro que a velocidade-up é mais devido à eliminação do desvio condicional que para eliminar o próprio operador instanceof; mas eu pensei que vale a pena mencionar.

Você está se concentrando na coisa errada. A diferença entre instanceof e qualquer outro método para verificar a mesma coisa, provavelmente até não ser mensurável. Se o desempenho é crítico, em seguida, Java é provavelmente o idioma errado. A principal razão é que você não pode controlar quando o VM decide que quer ir para coleta de lixo, o que pode levar a CPU a 100% durante vários segundos em um grande programa (MagicDraw 10 foi ótimo para isso). A menos que você está no controle de cada computador este programa será executado em que você não pode garantir qual versão do JVM será em, e muitos dos mais velhos tinham grandes problemas de velocidade. Se é um pequeno aplicativo você pode ser ok com Java, mas se você está constantemente lendo e descartando dados, então você irá aviso quando os GC entra em ação.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top