Antworten:
Ja absolut. Das Nachschlagen einer Klasse durch Reflexion erfolgt nach Größenordnung teurer.
Zitieren von Javas Dokumentation zur Reflexion :
Da es sich bei der Reflektion um Typen handelt, die dynamisch aufgelöst werden, können bestimmte Optimierungen der virtuellen Java-Maschine nicht durchgeführt werden. Folglich haben reflektierende Operationen eine langsamere Leistung als ihre nicht reflektierenden Gegenstücke und sollten in Codeabschnitten vermieden werden, die in leistungsempfindlichen Anwendungen häufig aufgerufen werden.
Hier ist ein einfacher Test, den ich in 5 Minuten auf meinem Computer mit Sun JRE 6u10 gehackt habe:
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);
}
}
Mit diesen Ergebnissen:
35 // no reflection
465 // using reflection
Denken Sie daran, dass die Suche und die Instanziierung zusammen durchgeführt werden. In einigen Fällen kann die Suche auch überarbeitet werden. Dies ist jedoch nur ein einfaches Beispiel.
Selbst wenn Sie nur instanziieren, erhalten Sie dennoch einen Leistungseinbruch:
30 // no reflection
47 // reflection using one lookup, only instantiating
Wieder YMMV.
Ja, es ist langsamer.
Aber denken Sie an die verdammte Regel Nr. 1: OPEMATUROPTIMIERUNG IST DIE WURZEL ALLEN BÖSEN
(Nun, kann mit # 1 für DRY verbunden sein)
Ich schwöre, wenn jemand bei der Arbeit auf mich zukam und mich danach fragte, würde ich in den nächsten Monaten sehr wachsam über ihren Code sein.
Sie dürfen niemals optimieren, bis Sie sicher sind, dass Sie es brauchen. Bis dahin schreiben Sie einfach guten, lesbaren Code.
Oh, und ich meine auch nicht, dummen Code zu schreiben. Denken Sie nur an die sauberste Art und Weise, wie Sie dies tun können - kein Kopieren und Einfügen usw. (Seien Sie immer noch vorsichtig bei Dingen wie inneren Schleifen und verwenden Sie die Sammlung, die Ihren Anforderungen am besten entspricht. Das Ignorieren dieser Programme ist keine "nicht optimierte" Programmierung , es ist "schlechte" Programmierung)
Es macht mich verrückt, wenn ich Fragen wie diese höre, aber dann vergesse ich, dass jeder alle Regeln selbst lernen muss, bevor er sie wirklich versteht. Sie erhalten es, nachdem Sie einen Monat lang mit dem Debuggen von etwas "Optimiertem" verbracht haben.
BEARBEITEN:
In diesem Thread ist etwas Interessantes passiert. Überprüfen Sie die Antwort Nr. 1. Dies ist ein Beispiel dafür, wie leistungsfähig der Compiler bei der Optimierung von Dingen ist. Der Test ist vollständig ungültig, da die nicht reflektierende Instanziierung vollständig herausgerechnet werden kann.
Lektion? Optimieren Sie NIEMALS, bis Sie eine saubere, sauber codierte Lösung geschrieben und bewiesen haben, dass sie zu langsam ist.
Möglicherweise stellen Sie fest, dass A a = new A () von der JVM optimiert wird. Wenn Sie die Objekte in ein Array einfügen, sind sie nicht so leistungsfähig. ;) Die folgenden Drucke ...
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);
}
}
Dies deutet darauf hin, dass der Unterschied auf meinem Computer etwa 150 ns beträgt.
Class.getDeclaredMethod
) und dann Method.invoke
mehrmals anrufe ? Benutze ich die Reflexion einmal oder so oft, wie ich sie aufrufe? Folgefrage, was ist, wenn es stattdessen Method
eine ist Constructor
und ich es Constructor.newInstance
mehrmals mache ?
Wenn wirklich etwas schneller als Reflexion benötigt wird und es nicht nur eine vorzeitige Optimierung ist, dann die Bytecode-Generierung mit ASM oder einer höheren Bibliothek eine Option. Das erstmalige Generieren des Bytecodes ist langsamer als nur das Reflektieren. Sobald der Bytecode generiert wurde, ist er genauso schnell wie normaler Java-Code und wird vom JIT-Compiler optimiert.
Einige Beispiele für Anwendungen, die die Codegenerierung verwenden:
Das Aufrufen von Methoden für von CGLIB generierte Proxys ist etwas schneller als die dynamischen Proxys von Java , da CGLIB Bytecode für seine Proxys generiert, dynamische Proxys jedoch nur Reflexion verwenden ( ich habe gemessen, dass CGLIB bei Methodenaufrufen etwa 10-mal schneller ist, aber das Erstellen der Proxys war langsamer).
JSerial generiert einen Bytecode zum Lesen / Schreiben der Felder serialisierter Objekte, anstatt Reflektion zu verwenden. Es gibt einige Benchmarks auf der Website von JSerial.
Ich bin nicht 100% sicher (und ich habe jetzt keine Lust, die Quelle zu lesen), aber ich denke, Guice generiert Bytecode, um die Abhängigkeitsinjektion durchzuführen. Korrigiere mich, wenn ich falsch liege.
"Signifikant" ist völlig kontextabhängig.
Wenn Sie Reflection verwenden, um ein einzelnes Handlerobjekt basierend auf einer Konfigurationsdatei zu erstellen, und dann den Rest Ihrer Zeit damit verbringen, Datenbankabfragen auszuführen, ist dies unbedeutend. Wenn Sie eine große Anzahl von Objekten durch Reflexion in einer engen Schleife erstellen, ist dies von Bedeutung.
Im Allgemeinen sollte die Designflexibilität (wo erforderlich!) Die Verwendung von Reflexion und nicht die Leistung fördern. Um jedoch festzustellen, ob die Leistung ein Problem darstellt, müssen Sie ein Profil erstellen, anstatt willkürliche Antworten von einem Diskussionsforum zu erhalten.
Es gibt einen gewissen Overhead bei der Reflexion, aber auf modernen VMs ist er viel kleiner als früher.
Wenn Sie Reflektion verwenden, um jedes einfache Objekt in Ihrem Programm zu erstellen, stimmt etwas nicht. Es gelegentlich zu verwenden, wenn Sie einen guten Grund haben, sollte überhaupt kein Problem sein.
Ja, es gibt einen Leistungseinbruch bei der Verwendung von Reflection, aber eine mögliche Problemumgehung für die Optimierung ist das Zwischenspeichern der Methode:
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");
wird darin enden, dass:
[Java] Das 1000000-malige reflexive Aufrufen der Methode mit Lookup dauerte 5618 Millis
[Java] Das 1000000-malige reflexive Aufrufen der Methode mit dem Cache dauerte 270 Millis
Die Reflexion ist langsam, obwohl die Objektzuweisung nicht so hoffnungslos ist wie andere Aspekte der Reflexion. Um eine gleichwertige Leistung mit reflexionsbasierter Instanziierung zu erzielen, müssen Sie Ihren Code schreiben, damit der JIT erkennen kann, welche Klasse instanziiert wird. Wenn die Identität der Klasse nicht ermittelt werden kann, kann der Zuordnungscode nicht eingefügt werden. Schlimmer noch, die Escape-Analyse schlägt fehl und das Objekt kann nicht gestapelt werden. Wenn Sie Glück haben, kann die Laufzeitprofilerstellung der JVM Abhilfe schaffen, wenn dieser Code heiß wird, und dynamisch bestimmen, welche Klasse vorherrscht, und für diese optimiert werden.
Beachten Sie, dass die Mikrobenchmarks in diesem Thread stark fehlerhaft sind. Nehmen Sie sie daher mit einem Körnchen Salz. Der mit Abstand am wenigsten fehlerhafte ist der von Peter Lawrey: Er führt Aufwärmläufe durch, um die Methoden zu verbessern, und er besiegt (bewusst) die Fluchtanalyse, um sicherzustellen, dass die Zuweisungen tatsächlich erfolgen. Sogar das hat seine Probleme: Zum Beispiel kann erwartet werden, dass die enorme Anzahl von Array-Speichern Caches und Speicherpuffer besiegt, so dass dies meistens ein Speicher-Benchmark wird, wenn Ihre Zuweisungen sehr schnell sind. (Ein großes Lob an Peter, dass er zu dem Schluss gekommen ist, dass der Unterschied "150 ns" und nicht "2,5x" ist. Ich vermute, dass er so etwas für seinen Lebensunterhalt tut.)
Interessanterweise führt die Einstellung von setAccessible (true), bei der die Sicherheitsüberprüfungen übersprungen werden, zu einer Kostenreduzierung von 20%.
Ohne setAccessible (true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
Mit setAccessible (true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
Aufrufe ausgeführt werden?
setAccessible()
kann es im Allgemeinen viel mehr Unterschiede geben, insbesondere bei Methoden mit mehreren Argumenten, daher sollte es immer aufgerufen werden.
Ja, es ist deutlich langsamer. Wir haben einen Code ausgeführt, der dies tat, und obwohl mir die Metriken derzeit nicht zur Verfügung stehen, war das Endergebnis, dass wir diesen Code umgestalten mussten, um keine Reflektion zu verwenden. Wenn Sie wissen, was die Klasse ist, rufen Sie einfach den Konstruktor direkt auf.
In doReflection () ist der Overhead aufgrund von Class.forName ("misc.A") (für das eine Klassensuche erforderlich wäre, die möglicherweise den Klassenpfad auf dem Dateisystem scannt) und nicht die für die Klasse aufgerufene newInstance (). Ich frage mich, wie die Statistiken aussehen würden, wenn Class.forName ("misc.A") nur einmal außerhalb der for-Schleife ausgeführt wird. Dies muss nicht unbedingt bei jedem Aufruf der Schleife erfolgen.
Ja, es wird immer langsamer sein, ein Objekt durch Reflektion zu erstellen, da die JVM den Code zur Kompilierungszeit nicht optimieren kann. Weitere Informationen finden Sie in den Sun / Java Reflection-Tutorials .
Siehe diesen einfachen 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()
) von der Instanziierung (newInstance ()) trennen sollten , da sie sich in ihren Leistungsmerkmalen erheblich unterscheiden und Sie die wiederholte Suche in einem gut gestalteten System gelegentlich vermeiden können.
Oft können Sie Apache Commons BeanUtils oder PropertyUtils verwenden, die eine Introspektion durchführen (im Grunde werden die Metadaten über die Klassen zwischengespeichert, sodass sie nicht immer Reflektion verwenden müssen).
Ich denke, es hängt davon ab, wie leicht / schwer die Zielmethode ist. Wenn die Zielmethode sehr leicht ist (z. B. Getter / Setter), kann sie 1 bis 3 Mal langsamer sein. Wenn die Zielmethode etwa 1 Millisekunde oder mehr dauert, ist die Leistung sehr nahe. Hier ist der Test, den ich mit Java 8 und Reflectasm durchgeführt habe :
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;
}
}
}
Der vollständige Testcode ist unter GitHub verfügbar: ReflectionTest.java