O impacto no desempenho do uso de instanceof em Java

314

Estou trabalhando em um aplicativo e uma abordagem de design envolve o uso extremamente pesado do instanceofoperador. Embora eu saiba que o design de OO geralmente tenta evitar o uso instanceof, essa é uma história diferente e esta questão está puramente relacionada ao desempenho. Eu queria saber se há algum impacto no desempenho? É tão rápido quanto ==?

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

Uma das outras maneiras pelas quais pensei em resolvê-lo foi usar uma primitiva inteira "id do tipo" e usar uma máscara de bits para representar categorias das subclasses e, em seguida, fazer uma comparação de máscara de bits das subclasses "tipo id" para um máscara constante que representa a categoria.

De instanceofalguma forma, a JVM é otimizada para ser mais rápida que isso? Eu quero me ater ao Java, mas o desempenho do aplicativo é crítico. Seria legal se alguém que já passou por esse caminho antes pudesse oferecer alguns conselhos. Estou falando demais ou focando na coisa errada para otimizar?

Josh
fonte
81
Acho que o objetivo da pergunta, no entanto, era deixar de lado a questão das melhores práticas de OO e examinar o desempenho.
Dave L.
3
@ Dave L. Normalmente, eu concordo, mas o OP menciona que ele está procurando algumas técnicas gerais de otimização e não tem certeza se o problema está relacionado a 'instanceof'. Acho que vale a pena mencionar o design 'correto' para que ele possa definir as duas opções.
Outlaw Programmer
51
Ugh ... Por que todas as respostas não atendem ao objetivo e fornecem a mesma velha retórica de Knuth sobre otimização? Sua pergunta é sobre se instanceof é significativamente / surpreendentemente mais lento do que verificar o objeto de classe com ==, e descobri que não.
Gubby 12/05
3
O desempenho de instanceof e casting é muito bom. Eu postei alguns tempo em Java7 em torno de diferentes abordagens para o problema aqui: stackoverflow.com/questions/16320014/...
Wheezil

Respostas:

268

Os compiladores JVM / JIC modernos removeram o impacto no desempenho da maioria das operações tradicionalmente "lentas", incluindo instância de, manipulação de exceção, reflexão, etc.

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

Steve
fonte
13
JVM / JIC moderna. Você pode mencionar de qual versão java essas otimizações foram abordadas?
Ravisha
138
Sempre há alguém que cita Knuth quando o assunto é desempenho ... Esquecendo, que Knuth também afirmou (no mesmo artigo) "Nas disciplinas de engenharia estabelecidas, uma melhoria de 12%, facilmente obtida, nunca é considerada marginal e acredito o mesmo ponto de vista. deve prevalecer na engenharia de software ", quase todo o seu trabalho foi sobre eficiência de algoritmos e ele escreveu algoritmos em assembly para (entre outros) alcançar melhor desempenho. Meh ...
kgadek 01/09/11
4
Um aparte aqui, mas seria try { ObjT o = (ObjT)object } catch (e) { no not one of these }mais rápido mais lento ??
peterk
35
Se "objeto" é uma instância do ObjT, a transmissão é um pouco mais rápida do que a instância de um exemplo, mas a diferença encontrada pelo meu teste rápido foi de 10 a 20ms em 10.000.000 de iterações. Se "objeto" não for um ObjT, porém, capturar a exceção foi mais de 3000x mais lento - mais de 31.000ms vs ~ 10ms para a instânciaof.
Steve Steve
19
um argumento tão forte, sem qualquer "referência", é completamente inútil porque apenas opinativo.
Marcorossi 17/04/2013
279

Abordagem

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

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

Usei o jmh para executar o benchmark com 100 chamadas de aquecimento, 1000 iterações sob medição e 10 garfos. Portanto, cada opção foi medida 10.000 vezes, o que leva 12:18:57 para executar todo o benchmark no meu MacBook Pro com o macOS 10.12.4 e o 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: existe uma versão anterior desta resposta e minha referência .

Resultados

| Operação Tempo de execução em nanossegundos por operação | Em relação a instanceof |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANCEOF | 39,598 ± 0,022 ns / op | 100,00% |
| GETCLASS | 39.687 ± 0,021 ns / op | 100,22% |
| TIPO 46.295 ± 0,026 ns / op | 116,91% |
| OO 48.078 ± 0,026 ns / op | 121,42% |

tl; dr

No Java 1.8, instanceofé a abordagem mais rápida, embora getClass()seja muito próxima.

Michael Dorner
fonte
58
+0.(9)pela ciência!
16
+ os outros 0,1 de mim: D
Tobias Reich
14
@TobiasReich Então conseguimos +1.0(9). :)
Pavel
9
Eu não acho que isso mede nada significativo. O código mede o uso System.currentTimeMillis()em uma operação que não é muito mais do que uma única chamada de método, que deve dar muito a baixa precisão. Use uma estrutura de benchmark como JMH !
Lii
6
Ou apenas faça o timing de todo o bilhão de chamadas, e não por chamada.
usar o seguinte comando
74

Acabei de fazer um teste simples para ver como o desempenho instanceOf está se comparando a uma simples chamada s.equals () para um objeto string com apenas uma letra.

em um loop de 10.000.000, a instanceOf me deu 63-96ms, e a sequência igual me deu 106-230ms

Eu usei o java jvm 6.

Portanto, no meu teste simples, é mais rápido executar um instanceOf do que uma comparação de uma cadeia de caracteres.

usar .equals () do número inteiro em vez de da sequência me deu o mesmo resultado, somente quando usei o == i foi mais rápido que instanceOf por 20ms (em um loop de 10.000.000)


fonte
4
Seria possível para você postar o código aqui? Isso seria demais!
O Alquimista
7
Como o instanceOf se compara ao despacho da função polimórfica?
Chris
21
Por que você compara instanceof com um String.equals ()? Se você quiser verificar o tipo que você tem que a object.getClass () equals (SomeType.class).
marsbear
4
@marsbear equals()não resolve , porque a subclassificação; você precisa isAssignableFrom().
David Moles
1
@ marsbear Certo, mas esse não é um teste melhor do que o OP estava pedindo.
David Moles
20

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

  1. O número de classes possíveis para as quais o operador instanceof poderia retornar true
  2. A distribuição dos seus dados - a maioria das instâncias de operações foi resolvida na primeira ou na segunda tentativa? Você desejará colocar suas chances de retornar operações verdadeiras primeiro.
  3. O ambiente de implementação. A execução em uma Sun Solaris VM é significativamente diferente da JVM do Windows da Sun. O Solaris será executado no modo 'servidor' por padrão, enquanto o Windows será executado no modo cliente. As otimizações de JIT no Solaris tornarão todos os acessos ao método iguais.

Criei uma marca de microbench para quatro métodos diferentes de envio . Os resultados do Solaris são os seguintes, com o número menor sendo mais rápido:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
brianegge
fonte
18

Respondendo à sua última pergunta: a menos que um criador de perfil informe, você gasta uma quantidade ridícula de tempo em uma instância de: Sim, você está dando uma olhada.

Antes de pensar em otimizar algo que nunca precisou ser otimizado: escreva seu algoritmo da maneira mais legível e execute-o. Execute-o até que o compilador jit tenha a chance de otimizá-lo. Se você tiver problemas com esse trecho de código, use um criador de perfil para saber onde obter o máximo e otimizar isso.

Em tempos de otimizadores altamente otimizados, é provável que suas suposições sobre gargalos estejam completamente erradas.

E no verdadeiro espírito desta resposta (na qual acredito sinceramente): absolutamente não sei como a instância e o == se relacionam quando o compilador jit teve a chance de otimizá-la.

Esqueci: nunca meça a primeira corrida.

Olaf Kock
fonte
1
Mas o pôster original mencionou que o desempenho era fundamental para este aplicativo, portanto, não é razoável otimizar no início dessa situação. Em outras palavras, você não escreveria um jogo em 3D no GWBasic e, no final, diria: ok, vamos começar a otimizar isso, o primeiro passo é portá-lo para c ++.
usar o seguinte comando
O GWBasic pode ser um ótimo começo para jogos 3D, se houver bibliotecas adequadas disponíveis. Mas isso de lado (como é um argumento artificial): OP não está pedindo uma reescrita completa como otimização. Trata-se de uma única construção em que nem sabemos se o impacto é significativo (mesmo se houver uma maneira com melhor desempenho para fazer o mesmo na versão atual do compilador ). Estou firmemente de pé atrás de c2.com/cgi/wiki?ProfileBeforeOptimizing e minha resposta. A otimização preliminar é a raiz de todo mal! Ele torna a manutenção mais difícil - e manutenção é o aspecto que vale otimização
Olaf Kock
15

Tenho a mesma pergunta, mas como não encontrei 'métricas de desempenho' para casos de uso semelhantes aos meus, fiz mais alguns exemplos de código. No meu hardware e no Java 6 e 7, a diferença entre instanceof e switch 10mln iterations é

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

Portanto, instanceof é realmente mais lento, especialmente em um grande número de instruções if-else-if, porém a diferença será insignificante dentro do aplicativo 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()));
    }
}
Xtra Coder
fonte
Qual resultado foi o java 6 e qual foi o java 7? Você revisitou isso no Java 8? Mais significativamente aqui, você está comparando um comprimento, se for instanceofs, ao essencial de uma declaração de caso em ints. Eu acho que esperamos que um switch int seja rápido.
precisa saber é o seguinte
1
Não me lembro exatamente o que estava acontecendo há cinco anos - acho que o Java 6 e o ​​Java 7 tiveram resultado semelhante, é por isso que existe apenas um resultado fornecido (desde que duas linhas sejam para diferentes profundidades da hierarquia de classes) ... e não , não tentei comparar com o Java 8. Todo o código de teste é fornecido - você pode copiá-lo / colá-lo e verificar os ambientes de que precisa (observe - hoje em dia eu usaria o teste JMH para isso).
Xtra Coder
9

instanceof é realmente rápido, seguindo apenas algumas instruções da CPU.

Aparentemente, se uma classe Xnão tiver subclasses carregadas (a JVM sabe), ela instanceofpoderá ser otimizada como:

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

O custo principal é apenas uma leitura!

Se houver Xsubclasses carregadas, são necessárias mais algumas leituras; eles provavelmente estão co-localizados, portanto o custo extra também é muito baixo.

Boas notícias, pessoal!

irreputável
fonte
2
pode ser otimizado ou está otimizado? fonte?
@vaxquis lata como seu específico JVM impl
RecursiveExceptionException
@itzJanuary suspirar você perdeu o ponto da minha pergunta aqui: todo mundo sabe que o compilador pode otimizar foo- mas é foo, na verdade, atualmente otimizado pela Oracle javac / VM - ou é apenas possível que ele vai fazer isso no futuro? Além disso, perguntei ao respondente que ele tem alguma fonte de suporte (seja docs, código fonte, blog de desenvolvimento) documentando que realmente pode ser otimizada ou otimizada ? Sem ela, essa resposta é apenas uma reflexão aleatória sobre o que o compilador pode fazer.
@vaxquis Você nunca mencionou a VM do Hotspot, mas nesse caso eu não sei se ela está "otimizada".
RecursiveExceptionException
1
Recentemente, leia que o JIT (JVM 8) otimizará um site de chamadas para 1 ou 2 tipos por chamadas diretas, mas reverterá para a vtable se mais de dois tipos reais forem encontrados. Portanto, apenas ter dois tipos concretos passando por um site de chamada em tempo de execução representa uma vantagem de desempenho.
precisa saber é o seguinte
5

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

Apocalisp
fonte
5

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

Por que é que? Porque o que provavelmente vai acontecer é que você tem várias interfaces, que fornecem alguma funcionalidade (digamos, interfaces x, ye z) e alguns objetos a serem manipulados que podem (ou não) implementar uma dessas interfaces ... mas Não diretamente. Digamos, por exemplo, eu tenho:

w estende x

A implementa w

B estende A

C estende B, implementa y

D estende C, implementa z

Suponha que eu esteja processando uma instância de D, o objeto d. A computação (d instância de x) exige que o d.getClass () faça um loop pelas interfaces implementadas para saber se um é == x, e se não o fizer novamente recursivamente para todos os seus ancestrais ... No nosso caso, se você fizer uma primeira exploração ampla dessa árvore, produzirá pelo menos 8 comparações, supondo que y e z não estendam nada ...

A complexidade de uma árvore de derivação do mundo real provavelmente será maior. Em alguns casos, o JIT pode otimizar a maior parte, se puder resolver com antecedência d como sendo, em todos os casos possíveis, uma instância de algo que estende x. Realisticamente, no entanto, você passará por essa árvore na maior parte do tempo.

Se isso se tornar um problema, sugiro usar um mapa de manipulador, vinculando a classe concreta do objeto a um fechamento que faça o manuseio. Ele remove a fase transversal da árvore em favor de um mapeamento direto. No entanto, lembre-se de que, se você configurou um manipulador para C.class, meu objeto d acima não será reconhecido.

aqui estão meus 2 centavos, espero que eles ajudem ...


fonte
5

instanceof é muito eficiente, portanto é improvável que seu desempenho seja prejudicado. No entanto, o uso de muitas instâncias sugere um problema de design.

Se você pode usar xClass == String.class, isso é mais rápido. Nota: você não precisa de instanceof para as aulas finais.

Peter Lawrey
fonte
1
Mas o que você quer dizer com "não precisa de instanceof para as aulas finais"?
Pacerier
Uma classe final não pode ter subclasses. Nesse caso, x.getClass() == Class.classé o mesmo quex instanceof Class
Peter Lawrey 6/12/12
Legal, supondo que x não seja nulo, qual você prefere?
218 Pacerier
Bom ponto. Depende se eu espero xser null, suponho. (Ou o que for mais clara)
Peter Lawrey
Hmm, acabei de perceber que poderíamos usar java.lang.class.isAssignableFrom também, você sabe se o instanceof keyword usa internamente funções como essas?
Pacerier
4

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

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

Você pode substituir isso por

o.doEverything();

e, em seguida, tenha a implementação de "doEverything ()" na classe1 chamada "doThis ()" e na classe2 chamada "doThat ()" e assim por diante.

Paul Tomblin
fonte
11
Mas às vezes você não pode. Se você está implementando uma interface que aceita um objeto e precisa dizer qual é o tipo, instanceof é realmente a única opção. Você pode tentar transmitir, mas instanceof é geralmente mais limpo.
Herms
4

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

Não devo dizer que, se você tem uma opção em que está testando se um objeto é uma instância de alguma subclasse, seu design pode precisar ser reformulado. Considere incluir o comportamento específico da subclasse nas próprias subclasses.

Programador foragido
fonte
4

Demian e Paul mencionam um bom ponto; no entanto , o posicionamento do código a ser executado depende realmente de como você deseja usar os dados ...

Sou um grande fã de pequenos objetos de dados que podem ser usados ​​de várias maneiras. Se você seguir a abordagem de substituição (polimórfica), seus objetos poderão ser usados ​​apenas "de uma maneira".

É aqui que entram os padrões ...

Você pode usar o despacho duplo (como no padrão do visitante) para solicitar que cada objeto "chame você" passando por si mesmo - isso resolverá o tipo do objeto. No entanto (novamente), você precisará de uma classe que possa "fazer coisas" com todos os subtipos possíveis.

Prefiro usar um padrão de estratégia, onde você pode registrar estratégias para cada subtipo que deseja manipular. Algo como o seguinte. Observe que isso ajuda apenas a correspondências exatas de tipo, mas tem a vantagem de ser extensível - colaboradores de terceiros podem adicionar seus próprios tipos e manipuladores. (Isso é bom para estruturas dinâmicas como OSGi, onde novos pacotes configuráveis ​​podem ser adicionados)

Espero que isso inspire 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);
    }
}
Scott Stanchfield
fonte
4

Eu escrevo um teste de desempenho baseado no jmh-java-benchmark-archetype: 2.21. O JDK é openjdk e a versão é 1.8.0_212. A máquina de teste é o mac pro. O 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

O resultado mostra que: getClass é melhor que instanceOf, o que é contrário a outro teste. No entanto, não sei por que.

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();
}
}
salexinx
fonte
Se eu deveria especular, o que a instância faz é sem dúvida mais complexa. Uma verificação de getClass () == fará uma verificação precisa de 1: 1, em que instanceof verifica uma hierarquia, por exemplo, a instância myHashSet da coleção passaria, mas myHashSet.getClass () == Collection.class não. Essencialmente, elas não são operações equivalentes, então não estou muito surpreso que o desempenho também seja diferente.
AMTerp
3

É difícil dizer como uma certa JVM implementa uma instância de, mas na maioria dos casos, os objetos também são comparáveis ​​a estruturas e classes e cada estrutura de objeto tem um ponteiro para a estrutura de classe da qual é uma instância. Então, na verdade, instanceof for

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)

supondo que um compilador JIT esteja instalado e faça um trabalho decente.

Considerando que isso é apenas acessar um ponteiro, obter um ponteiro em um determinado deslocamento que o ponteiro aponta e compará-lo com outro ponteiro (que é basicamente o mesmo que testar se números de 32 bits são iguais), eu diria que a operação pode realmente seja muito rápido.

Não precisa, no entanto, depende muito da JVM. No entanto, se essa for a operação de gargalo no seu código, consideraria a implementação da JVM bastante ruim. Mesmo um que não possui compilador JIT e apenas interpreta o código deve poder fazer uma instância de teste praticamente em pouco tempo.

Mecki
fonte
1
Ele não precisa descobrir se o herda de java.lang.String?
WW.
1
Por isso eu disse que "poderia" ser tão rápido. Na realidade, ele executa um loop, primeiro checando iAmInstanceOf com relação à classe em questão, depois sobe a árvore de herança de o e repete essa verificação para todas as superclasses de o (para que seja necessário executar esse loop algumas vezes para uma partida)
Mecki 02/02/09
3

Voltarei a você em caso de desempenho. Mas uma maneira de evitar um problema (ou a falta dele) seria criar uma interface pai para todas as subclasses nas quais você precisa executar uma instância. A interface será um super conjunto de todos os métodos nas subclasses para os quais você precisa executar a instância de verificação. Onde um método não se aplica a uma subclasse específica, simplesmente forneça uma implementação fictícia desse método. Se não entendi mal a questão, foi assim que resolvi o problema no passado.

Jose Quijada
fonte
2

Instancia de é um aviso de design orientado a objetos ruim.

JVMs atuais significam que instanceOf não é uma grande preocupação de desempenho por si só. Se você está usando muito, especialmente para a funcionalidade principal, provavelmente é hora de analisar o design. Os ganhos de desempenho (e simplicidade / capacidade de manutenção) da refatoração para um design melhor superam muito os ciclos reais do processador gastos na chamada instanceOf real .

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

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

Como uma arquitetura ruim, uma opção melhor seria ter SomeObject como a classe pai de duas classes filho, em que cada classe filha substitui um método (doSomething) para que o código parecesse:

Someobject.doSomething();
Demian Krige
fonte
61
Eu estou ciente disso. Não foi o que eu perguntei.
217 Josh
Tem certeza se para votar isso ou não, pois é um ponto bom, mas não responde a pergunta feita ...
jklp
7
Eu acho que o exemplo de código é realmente muito ruim: você não pode estender a classe Double e também não pode derivar Double de outra classe. Se você tivesse usado outras classes para o exemplo, teria sido bom.
Lena Schimmel
6
Além disso, se as classes filho de SomeObject forem objetos de valor, não será necessário colocar a lógica nelas. Por exemplo, Torta e Roast podem não ser o local correto para a lógica putInOven () e putInMouth ().
sk.
auto cozinhar torta e asse seria fantástico embora
binboavetonik
2

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

if(a instanceof AnyObject){
}

é mais rápido como:

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

Outra coisa é se você precisar cascatear várias instâncias. Em seguida, um switch que chama apenas uma vez getType () é mais rápido.

Horcrux7
fonte
1

Se a velocidade é o seu único objetivo, o uso de constantes int para identificar subclasses parece reduzir um milissegundo de 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;
}

péssimo design de OO, mas se sua análise de desempenho indicar que é nesse ponto que você encontra o gargalo, talvez. No meu código, o código de despacho ocupa 10% do tempo total de execução e isso talvez tenha contribuído para uma melhoria de velocidade total de 1%.

Salix alba
fonte
0

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

Você deve falar mais sobre o problema, talvez possa usar uma loja associativa, por exemplo, um Mapa <Classe, Objeto> se estiver interessado apenas nos tipos concretos.

Karl
fonte
0

Com relação à nota de Peter Lawrey de que você não precisa de uma aula para as aulas finais e pode usar apenas uma igualdade de referência, tenha cuidado! Mesmo que as classes finais não possam ser estendidas, elas não são garantidas para serem carregadas pelo mesmo carregador de classe. Use apenas x.getClass () == SomeFinal.class ou seu tipo, se tiver certeza absoluta de que existe apenas um carregador de classe em jogo para essa seção do código.


fonte
4
Se uma classe é carregada por um carregador de classes diferente, também não acho que instanceof corresponderá.
Peter Lawrey
0

Também prefiro uma abordagem enum, mas usaria uma classe base abstrata para forçar as subclasses a implementar o getType()método.

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;
    }
  }
}
Mike
fonte
0

Eu pensei que valeria a pena enviar um contra-exemplo ao consenso geral nesta página de que "instanceof" não é caro o suficiente para se preocupar. Eu descobri que tinha algum código em um loop interno que (em alguma tentativa histórica de otimização)

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

onde chamar head () em um SingleItem retorna o valor inalterado. Substituindo o código por

seq = seq.head();

me dá uma aceleração de 269ms a 169ms, apesar do fato de que algumas coisas muito pesadas estão acontecendo no loop, como a conversão de string para duplo. É possível, é claro, que a aceleração se deva mais à eliminação da ramificação condicional do que à instância do próprio operador; mas achei que vale a pena mencionar.

Michael Kay
fonte
Isso pode ser por causa do ifpróprio. Se a distribuição de trues e falses é quase igual, a execução especulativa se torna inútil, o que leva a atrasos significativos.
Dmytro
-4

Você está se concentrando na coisa errada. A diferença entre instanceof e qualquer outro método para verificar a mesma coisa provavelmente nem seria mensurável. Se o desempenho é crítico, o Java provavelmente é a linguagem errada. O principal motivo é que você não pode controlar quando a VM decide coletar lixo, o que pode levar a CPU a 100% por vários segundos em um programa grande (o MagicDraw 10 foi ótimo para isso). A menos que você esteja no controle de todos os computadores em que este programa será executado, você não poderá garantir em qual versão da JVM estará, e muitos dos mais antigos tiveram 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ê vai notar quando os pontapés GC no.

abordagem
fonte
7
Isso é muito menos verdadeiro com relação aos algoritmos de coleta de lixo em java mais modernos do que costumava ser. Mesmo os algoritmos mais simples não se importam mais com a quantidade de memória que você descarta logo após usá-la - eles se importam com o quanto é retido nas coleções da geração jovem.
Bill Michell
3
Ótimo, exceto que estou na JVM mais recente e meu computador ainda rasteja quando o GC é executado. Em um servidor ram de núcleo duplo, 3 GB. Java não é uma linguagem a ser usada se o desempenho realmente importa.
tloach
@ David: Você não precisa ter problemas em tempo real quando seu aplicativo desaparece por um período de tempo. Um divertido que eu encontrei é um aplicativo java que se conectava a um fluxo TCP que morreu quando o GC foi executado porque ele não fechou o fluxo primeiro e não conseguiu lidar com a sobrecarga do tráfego de rede ao voltar - ele seria imediatamente entra em um loop em que o GC é executado, quando o aplicativo é reiniciado, ele tenta agitar um monte de dados, o que fez com que a memória fique sem memória, o que acionou o GC etc. Java é ótimo para muitas tarefas, mas não para tarefas onde forte desempenho é um requisito.
tloach
6
O @tloach me parece um design de aplicativo ruim. você fala sobre "desempenho" como se fosse unidimensional. Já trabalhei com (e desenvolvi) vários aplicativos java que, por exemplo, tiveram desempenho no fornecimento de análises estatísticas interativas rápidas e visualização de conjuntos de dados muito grandes, ou desempenho no processamento de grandes volumes de transações muito rapidamente. "desempenho" não é apenas uma coisa, e o fato de alguém poder escrever um aplicativo que gerencia muito mal a memória e permite que o GC fique do seu jeito não significa que algo que exija "desempenho" deve ser escrito em outra coisa.
precisa