Estou trabalhando em um aplicativo e uma abordagem de design envolve o uso extremamente pesado do instanceof
operador. 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 instanceof
alguma 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?
fonte
Respostas:
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.
fonte
try { ObjT o = (ObjT)object } catch (e) { no not one of these }
mais rápido mais lento ??Abordagem
Eu escrevi um programa de benchmark para avaliar diferentes implementações:
instanceof
implementação (como referência)@Override
um método de testegetClass() == _.class
implementaçãoUsei 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
tl; dr
No Java 1.8,
instanceof
é a abordagem mais rápida, emboragetClass()
seja muito próxima.fonte
+0.(9)
pela ciência!+1.0(9)
. :)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 !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
equals()
não resolve , porque a subclassificação; você precisaisAssignableFrom()
.Os itens que determinarão o impacto no desempenho são:
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:
fonte
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.
fonte
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 é
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.
fonte
instanceof
é realmente rápido, seguindo apenas algumas instruções da CPU.Aparentemente, se uma classe
X
não tiver subclasses carregadas (a JVM sabe), elainstanceof
poderá ser otimizada como:O custo principal é apenas uma leitura!
Se houver
X
subclasses 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!
fonte
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.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.
fonte
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
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.
fonte
x.getClass() == Class.class
é o mesmo quex instanceof Class
x
sernull
, suponho. (Ou o que for mais clara)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:
Você pode substituir isso por
e, em seguida, tenha a implementação de "doEverything ()" na classe1 chamada "doThis ()" e na classe2 chamada "doThat ()" e assim por diante.
fonte
'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.
fonte
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 ...
fonte
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 é:
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:
fonte
É 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
pode ser tão rápido quanto o seguinte código C
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.
fonte
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.
fonte
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.
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:
fonte
Na versão Java moderna, o operador instanceof é mais rápido como uma simples chamada de método. Isso significa:
é mais rápido como:
Outra coisa é se você precisar cascatear várias instâncias. Em seguida, um switch que chama apenas uma vez getType () é mais rápido.
fonte
Se a velocidade é o seu único objetivo, o uso de constantes int para identificar subclasses parece reduzir um milissegundo de tempo
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%.
fonte
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.
fonte
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
Também prefiro uma abordagem enum, mas usaria uma classe base abstrata para forçar as subclasses a implementar o
getType()
método.fonte
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)
onde chamar head () em um SingleItem retorna o valor inalterado. Substituindo o código por
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.
fonte
if
próprio. Se a distribuição detrue
s efalse
s é quase igual, a execução especulativa se torna inútil, o que leva a atrasos significativos.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.
fonte