Respostas:
Sim absolutamente. Procurar uma classe via reflexão é, por magnitude , mais caro.
Citando a documentação do Java sobre reflexão :
Como a reflexão envolve tipos resolvidos dinamicamente, determinadas otimizações da máquina virtual Java não podem ser executadas. Conseqüentemente, as operações reflexivas têm desempenho mais lento do que as contrapartes não refletivas e devem ser evitadas nas seções de código que são chamadas com freqüência em aplicativos sensíveis ao desempenho.
Aqui está um teste simples que eu fiz em 5 minutos na minha máquina, executando o Sun JRE 6u10:
public class Main {
public static void main(String[] args) throws Exception
{
doRegular();
doReflection();
}
public static void doRegular() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = new A();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
public static void doReflection() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = (A) Class.forName("misc.A").newInstance();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
}
Com estes resultados:
35 // no reflection
465 // using reflection
Lembre-se de que a pesquisa e a instanciação são feitas juntas e, em alguns casos, a pesquisa pode ser refatorada, mas este é apenas um exemplo básico.
Mesmo se você apenas instanciar, ainda terá um impacto no desempenho:
30 // no reflection
47 // reflection using one lookup, only instantiating
Mais uma vez, YMMV.
Sim, é mais lento.
Mas lembre-se da maldita regra nº 1 - A otimização prematura é a raiz de todo o mal
(Bem, pode ser empatado com # 1 para DRY)
Juro que, se alguém viesse até mim no trabalho e me perguntasse isso, ficaria muito atento ao código deles nos próximos meses.
Você nunca deve otimizar até ter certeza de que precisa, até então, basta escrever um código legível e bom.
Ah, e também não quero escrever código estúpido. Basta pensar na maneira mais limpa possível de fazê-lo - sem copiar e colar, etc. (Ainda tenha cuidado com coisas como loops internos e usando a coleção que melhor se adapta às suas necessidades - Ignorar essas não é uma programação "não otimizada" , é "ruim" de programação)
Me assusta quando ouço perguntas como essa, mas depois esqueço que todo mundo precisa aprender todas as regras antes de realmente entender. Você o receberá depois de passar um mês depurando algo que alguém "Otimizou".
EDITAR:
Uma coisa interessante aconteceu neste tópico. Verifique a resposta nº 1, é um exemplo de quão poderoso o compilador é para otimizar as coisas. O teste é completamente inválido porque a instanciação não reflexiva pode ser completamente fatorada.
Lição? NUNCA otimize até escrever uma solução limpa e bem codificada e provar que é muito lenta.
Você pode achar que A a = new A () está sendo otimizado pela JVM. Se você colocar os objetos em uma matriz, eles não terão um desempenho tão bom. ;) As seguintes impressões ...
new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns
public class Run {
private static final int RUNS = 3000000;
public static class A {
}
public static void main(String[] args) throws Exception {
doRegular();
doReflection();
doRegular();
doReflection();
}
public static void doRegular() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = new A();
}
System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
public static void doReflection() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = A.class.newInstance();
}
System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
}
Isso sugere que a diferença é de cerca de 150 ns na minha máquina.
Class.getDeclaredMethod
) e depois ligar Method.invoke
várias vezes? Estou usando a reflexão uma ou tantas vezes quanto a invoco? Pergunta de acompanhamento, e se, em vez disso Method
, for um Constructor
e eu fizer Constructor.newInstance
várias vezes?
Se realmente houver necessidade de algo mais rápido que a reflexão e não for apenas uma otimização prematura, a geração de bytecode com ASM ou uma biblioteca de nível superior é uma opção. Gerar o bytecode pela primeira vez é mais lento do que apenas usar reflexão, mas depois que o bytecode foi gerado, ele é tão rápido quanto o código Java normal e será otimizado pelo compilador JIT.
Alguns exemplos de aplicativos que usam geração de código:
A chamada de métodos em proxies gerados pelo CGLIB é um pouco mais rápida que os proxies dinâmicos do Java , porque o CGLIB gera bytecode para seus proxies, mas os proxies dinâmicos usam apenas reflexão ( eu medi o CGLIB para ser cerca de 10x mais rápido nas chamadas de método, mas a criação dos proxies era mais lenta).
JSerial gera bytecode para ler / gravar os campos de objetos serializados, em vez de usar reflexão. Existem alguns benchmarks no site do JSerial.
Não tenho 100% de certeza (e não sinto vontade de ler a fonte agora), mas acho que o Guice gera bytecode para fazer a injeção de dependência. Corrija-me se eu estiver errado.
"Significativo" é inteiramente dependente do contexto.
Se você estiver usando a reflexão para criar um único objeto manipulador com base em algum arquivo de configuração e gastando o resto do seu tempo executando consultas no banco de dados, isso é insignificante. Se você estiver criando um grande número de objetos via reflexão em um loop apertado, sim, é significativo.
Em geral, a flexibilidade do projeto (quando necessário!) Deve direcionar o uso da reflexão, não do desempenho. No entanto, para determinar se o desempenho é um problema, é necessário criar um perfil, em vez de obter respostas arbitrárias em um fórum de discussão.
Há alguma sobrecarga na reflexão, mas é muito menor nas VMs modernas do que costumava ser.
Se você estiver usando a reflexão para criar todos os objetos simples do seu programa, algo está errado. Usá-lo ocasionalmente, quando você tiver um bom motivo, não deve ser um problema.
Sim, há um impacto no desempenho ao usar o Reflection, mas uma possível solução alternativa para otimização está armazenando em cache o método:
Method md = null; // Call while looking up the method at each iteration.
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md = ri.getClass( ).getMethod("getValue", null);
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis");
// Call using a cache of the method.
md = ri.getClass( ).getMethod("getValue", null);
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");
vai resultar em:
[java] O método de chamada 1000000 vezes reflexivamente com a pesquisa levou 5618 milis
[java] O método de chamada 1000000 vezes reflexivamente com o cache levou 270 milis
A reflexão é lenta, embora a alocação de objetos não seja tão desesperadora quanto outros aspectos da reflexão. Obter desempenho equivalente com instanciação baseada em reflexão exige que você escreva seu código para que o jit possa dizer qual classe está sendo instanciada. Se a identidade da classe não puder ser determinada, o código de alocação não poderá ser incorporado. Pior, a análise de escape falha e o objeto não pode ser alocado por pilha. Se você tiver sorte, a criação de perfil em tempo de execução da JVM poderá ser útil se esse código esquentar e determinar dinamicamente qual classe predomina e otimizar para essa.
Esteja ciente de que as marcas de microbench neste segmento são profundamente defeituosas, portanto, leve-as com um grão de sal. De longe, o menos defeituoso é o de Peter Lawrey: ele executa o aquecimento para obter os métodos executados e (conscientemente) derrota a análise de escape para garantir que as alocações estejam realmente ocorrendo. Mesmo que você tenha seus problemas, no entanto: por exemplo, pode-se esperar que um grande número de armazenamentos de array derrote caches e buffers de armazenamento, portanto, isso acabará sendo principalmente uma referência de memória se suas alocações forem muito rápidas. (Parabéns a Peter por ter chegado à conclusão certa: que a diferença é "150ns" em vez de "2,5x". Suspeito que ele faça esse tipo de coisa como meio de vida.)
Curiosamente, a definição de setAccessible (true), que ignora as verificações de segurança, tem uma redução de 20% no custo.
Sem setAccessible (true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
Com setAccessible (true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
invocações?
setAccessible()
pode haver muito mais diferenças em geral, especialmente para métodos com vários argumentos, portanto, sempre deve ser chamado.
Sim, é significativamente mais lento. Estávamos executando um código que fazia isso e, embora eu não tenha as métricas disponíveis no momento, o resultado final foi que tivemos que refatorar esse código para não usar reflexão. Se você souber qual é a classe, basta ligar diretamente para o construtor.
No doReflection () está a sobrecarga por causa de Class.forName ("misc.A") (que exigiria uma pesquisa de classe, potencialmente varrendo o caminho da classe no sistema de arquivos), em vez do newInstance () chamado na classe. Eu estou querendo saber como seriam as estatísticas se o Class.forName ("misc.A") for feito apenas uma vez fora do loop for, ele realmente não precisa ser feito para todas as chamadas do loop.
Sim, sempre será mais lento criar um objeto por reflexão, porque a JVM não pode otimizar o código no tempo de compilação. Veja os tutoriais Sun / Java Reflection para mais detalhes.
Veja este teste simples:
public class TestSpeed {
public static void main(String[] args) {
long startTime = System.nanoTime();
Object instance = new TestSpeed();
long endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
startTime = System.nanoTime();
try {
Object reflectionInstance = Class.forName("TestSpeed").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
}
}
Class.forName()
) da instância (newInstance ()), porque elas variam significativamente em suas características de desempenho e, ocasionalmente, você pode evitar a pesquisa repetida em um sistema bem projetado.
Freqüentemente, você pode usar o Apache commons BeanUtils ou PropertyUtils cuja introspecção (basicamente eles armazenam em cache os metadados sobre as classes para que eles nem sempre precisem usar a reflexão).
Eu acho que depende de quão leve / pesado o método de destino é. se o método de destino for muito leve (por exemplo, getter / setter), pode ser 1 a 3 vezes mais lento. se o método de destino demorar cerca de 1 milissegundo ou mais, o desempenho será muito próximo. aqui está o teste que fiz com o Java 8 e reflectasm :
public class ReflectionTest extends TestCase {
@Test
public void test_perf() {
Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult();
Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult();
Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult();
Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult();
Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult();
Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult();
Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult();
Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult();
}
public static class X {
public long m_01() {
return m_11();
}
public long m_02() {
return m_12();
}
public static long m_11() {
long sum = IntStream.range(0, 10).sum();
assertEquals(45, sum);
return sum;
}
public static long m_12() {
long sum = IntStream.range(0, 10000).sum();
assertEquals(49995000, sum);
return sum;
}
}
}
O código de teste completo está disponível no GitHub: ReflectionTest.java