Risposte:
Si assolutamente. Cercare una classe tramite la riflessione è, per grandezza , più costoso.
Citando la documentazione di Java sulla riflessione :
Poiché la riflessione implica tipi che vengono risolti dinamicamente, alcune ottimizzazioni della macchina virtuale Java non possono essere eseguite. Di conseguenza, le operazioni riflettenti hanno prestazioni più lente rispetto alle loro controparti non riflettenti e dovrebbero essere evitate in sezioni di codice che vengono chiamate frequentemente in applicazioni sensibili alle prestazioni.
Ecco un semplice test che ho hackerato in 5 minuti sulla mia macchina, eseguendo 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);
}
}
Con questi risultati:
35 // no reflection
465 // using reflection
Tieni presente che la ricerca e l'istanza vengono eseguite insieme e in alcuni casi la ricerca può essere rifattorizzata, ma questo è solo un esempio di base.
Anche se hai solo un'istanza, ottieni comunque un successo in termini di prestazioni:
30 // no reflection
47 // reflection using one lookup, only instantiating
Ancora una volta, YMMV.
Sì, è più lento.
Ma ricorda la dannata regola n. 1: L'OTTIMIZZAZIONE DELLE PREMATURE È LA RADICE DI TUTTO IL MALE
(Beh, potrebbe essere legato con il n. 1 per DRY)
Lo giuro, se qualcuno venisse da me al lavoro e mi chiedesse questo sarei molto attento al loro codice per i prossimi mesi.
Non devi mai ottimizzare fino a quando non sei sicuro di averne bisogno, fino ad allora, basta scrivere un buon codice leggibile.
Oh, e non intendo nemmeno scrivere codice stupido. Pensa solo al modo più pulito in cui puoi farlo - nessuna copia e incolla, ecc. (Fai ancora attenzione a cose come i loop interni e usa la collezione che si adatta meglio alle tue esigenze - Ignorare queste non è una programmazione "non ottimizzata" , è "cattiva" programmazione)
Mi fa impazzire quando sento domande del genere, ma poi dimentico che tutti devono imparare da soli tutte le regole prima di capirlo davvero. Lo otterrai dopo aver trascorso un mese a fare il debug di qualcosa di "ottimizzato".
MODIFICARE:
In questo thread è successa una cosa interessante. Controlla la risposta n. 1, è un esempio di quanto sia potente il compilatore nell'ottimizzare le cose. Il test non è completamente valido poiché l'istanza non riflettente può essere completamente fattorizzata.
Lezione? Non ottimizzare MAI fino a quando non hai scritto una soluzione pulita, ben codificata e provata che è troppo lenta.
È possibile che A a = new A () venga ottimizzato dalla JVM. Se metti gli oggetti in un array, non funzionano così bene. ;) Le seguenti stampe ...
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);
}
}
Questo suggerisce che la differenza è di circa 150 ns sulla mia macchina.
Class.getDeclaredMethod
) e poi chiamo Method.invoke
più volte? Sto usando il riflesso una volta o tutte le volte che lo invoco? Domanda di follow-up, cosa succede se invece di Method
è un Constructor
e lo faccio Constructor.newInstance
più volte?
Se c'è davvero bisogno di qualcosa di più veloce della riflessione, e non è solo un'ottimizzazione prematura, allora generazione di bytecode con ASM o una libreria di livello superiore è un'opzione. La generazione del bytecode la prima volta è più lenta rispetto al semplice utilizzo di reflection, ma una volta generato il bytecode, è veloce come il normale codice Java e sarà ottimizzato dal compilatore JIT.
Alcuni esempi di applicazioni che utilizzano la generazione di codice:
Invocare metodi su proxy generati da CGLIB è leggermente più veloce dei proxy dinamici di Java , perché CGLIB genera bytecode per i suoi proxy, ma i proxy dinamici usano solo la riflessione ( ho misurato CGLIB per essere circa 10 volte più veloce nelle chiamate di metodo, ma la creazione dei proxy è stata più lenta).
JSerial genera un bytecode per leggere / scrivere i campi di oggetti serializzati, invece di usare la riflessione. Ci sono alcuni benchmark sul sito di JSerial.
Non sono sicuro al 100% (e non mi va di leggere la fonte ora), ma penso che Guice generi bytecode per fare l'iniezione di dipendenza. Correggimi se sbaglio.
"Significativo" dipende interamente dal contesto.
Se stai usando reflection per creare un singolo oggetto gestore basato su alcuni file di configurazione e poi trascorri il resto del tempo a eseguire query sul database, allora è insignificante. Se stai creando un gran numero di oggetti tramite la riflessione in un ciclo stretto, quindi sì, è significativo.
In generale, la flessibilità del design (dove necessario!) Dovrebbe guidare il tuo uso della riflessione, non delle prestazioni. Tuttavia, per determinare se le prestazioni sono un problema, è necessario creare un profilo anziché ottenere risposte arbitrarie da un forum di discussione.
C'è un certo sovraccarico con la riflessione, ma è molto più piccolo nelle VM moderne di quanto non fosse in passato.
Se stai usando la riflessione per creare ogni semplice oggetto nel tuo programma, allora qualcosa non va. Usarlo di tanto in tanto, quando hai buone ragioni, non dovrebbe essere affatto un problema.
Sì, c'è un impatto sulle prestazioni quando si utilizza Reflection, ma una possibile soluzione per l'ottimizzazione è la memorizzazione nella cache del metodo:
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");
comporterà:
[java] Il metodo di chiamata 1000000 volte riflessivo con la ricerca ha richiesto 5618 millis
[java] Il metodo di chiamata 1000000 volte riflessivo con cache ha richiesto 270 millis
La riflessione è lenta, sebbene l'allocazione degli oggetti non sia così disperata come altri aspetti della riflessione. Per ottenere prestazioni equivalenti con un'istanza basata sulla riflessione è necessario scrivere il codice in modo che jit possa dire quale classe viene istanziata. Se non è possibile determinare l'identità della classe, non è possibile incorporare il codice di allocazione. Peggio ancora, l'analisi di escape ha esito negativo e l'oggetto non può essere allocato in pila. Se sei fortunato, la profilazione di runtime della JVM può venire in soccorso se questo codice si surriscalda e può determinare dinamicamente quale classe predomina e può ottimizzare per quella.
Ricorda che i microbench in questo filo sono profondamente imperfetti, quindi prendili con un granello di sale. Il meno imperfetto è di gran lunga quello di Peter Lawrey: esegue i riscaldamenti per ottenere i metodi corretti e sconfigge (consapevolmente) l'analisi di fuga per garantire che le allocazioni si stiano effettivamente verificando. Anche se uno ha i suoi problemi, però: ad esempio, ci si può aspettare che l'enorme numero di negozi di array sconfigga le cache e memorizzi i buffer, quindi si tratterà principalmente di un benchmark di memoria se le allocazioni sono molto veloci. (Complimenti a Peter per ottenere la conclusione giusta però: che la differenza è "150ns" piuttosto che "2.5x". Sospetto che faccia questo genere di cose per vivere.)
È interessante notare che l'impostazione di setAccessible (true), che salta i controlli di sicurezza, ha una riduzione dei costi del 20%.
Senza setAccessible (true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
Con setAccessible (true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
invocazioni?
setAccessible()
può avere molta più differenza in generale, specialmente per metodi con argomenti multipli, quindi dovrebbe sempre essere chiamato.
Sì, è significativamente più lento. Stavamo eseguendo un po 'di codice per farlo, e mentre al momento non ho le metriche disponibili, il risultato finale è stato che abbiamo dovuto refactoring quel codice per non usare la riflessione. Se sai qual è la classe, chiama direttamente il costruttore.
Nel doReflection () c'è l'overhead a causa di Class.forName ("misc.A") (che richiederebbe una ricerca di classe, potenzialmente scansionando il percorso della classe sul filsystem), piuttosto che il newInstance () chiamato sulla classe. Mi chiedo come sarebbero le statistiche se Class.forName ("misc.A") viene eseguito solo una volta al di fuori del for-loop, non è necessario farlo per ogni invocazione del loop.
Sì, sarà sempre più lento creare un oggetto riflesso perché la JVM non può ottimizzare il codice al momento della compilazione. Vedere i tutorial di Sun / Java Reflection per maggiori dettagli.
Vedi questo semplice test:
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()
) dall'istanza (newInstance ()), perché variano in modo significativo nelle loro caratteristiche prestazionali e occasionalmente è possibile evitare la ricerca ripetuta in un sistema ben progettato.
Spesso puoi usare i comuni di Apache BeanUtils o PropertyUtils quale introspezione (in pratica memorizzano nella cache i metadati sulle classi in modo che non debbano sempre usare la riflessione).
Penso che dipenda da quanto sia leggero / pesante il metodo target. se il metodo target è molto leggero (ad es. getter / setter), potrebbe essere 1 ~ 3 volte più lento. se il metodo target impiega circa 1 millisecondo o superiore, le prestazioni saranno molto vicine. ecco il test che ho fatto con Java 8 e reflectionasm :
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;
}
}
}
Il codice di prova completo è disponibile su GitHub: ReflectionTest.java