202 Stimmen

Java-Reflexionsleistung

Führt die Erstellung eines Objekts mit Hilfe von Reflexion anstelle des Aufrufs des Klassenkonstruktors zu erheblichen Leistungsunterschieden?

202voto

Yuval Adam Punkte 155168

Ja, absolut. Die Suche nach einer Klasse über Reflexion ist, nach Größenordnung teurer.

Zitat Javas Dokumentation zur Reflexion :

Da Reflection Typen beinhaltet, die dynamisch aufgelöst werden, können bestimmte Optimierungen der virtuellen Maschine von Java nicht durchgeführt werden. Folglich sind reflexive Operationen langsamer als ihre nicht-reflektierenden Gegenstücke und sollten in Codeabschnitten, die in leistungsempfindlichen Anwendungen häufig aufgerufen werden, vermieden werden.

Hier ist ein einfacher Test, den ich in 5 Minuten auf meinem Rechner mit Sun JRE 6u10 durchgeführt 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, und in einigen Fällen kann die Suche weggelassen werden, aber dies ist nur ein einfaches Beispiel.

Selbst wenn Sie nur instanziieren, haben Sie immer noch einen Leistungseinbruch:

30 // no reflection
47 // reflection using one lookup, only instantiating

Auch hier gilt: YMMV.

5 Stimmen

Auf meinem Rechner erreicht der Aufruf .newInstance() mit nur einem Class.forName()-Aufruf etwa 30 Punkte. Je nach VM-Version kann der Unterschied mit einer geeigneten Caching-Strategie geringer sein, als Sie denken.

0 Stimmen

Auf meinem System (ein Dual-Core 2,4 GHz) sehe ich Zeiten von - Instantiate: 7,8, Reflect 1: 171,9 und Reflect 2: 3181,3 (wobei Reflect 2 die Reflektion wie oben und 1 die Reflektion mit forName ist, die nur einmal durchgeführt wird - alle Zeiten gelten für 1.000.000 Iterationen).

0 Stimmen

Außerdem hängt dieser Leistungsfaktor sehr stark von den Kosten der Objektinitialisierung ab, d. h. von der im Konstruktor geleisteten Arbeit.

107voto

Bill K Punkte 61074

Ja, es ist langsamer.

Aber denken Sie an die verdammte Regel Nr. 1 - PRÄMATISCHE OPTIMIERUNG IST DIE WURZEL ALLEN Übels

(Naja, kann mit Platz 1 für DRY gleichziehen)

Ich schwöre, wenn mich jemand bei der Arbeit darauf ansprechen würde, würde ich in den nächsten Monaten sehr genau auf seinen Code achten.

Optimieren Sie nie, bevor Sie nicht sicher sind, dass Sie es brauchen, und schreiben Sie bis dahin einfach guten, lesbaren Code.

Oh, und ich meine auch nicht, dummen Code zu schreiben. Denken Sie einfach an die sauberste Art und Weise, wie Sie es tun können - kein Kopieren und Einfügen usw. (Seien Sie dennoch vorsichtig mit Dingen wie inneren Schleifen und der Verwendung der Sammlung, die am besten zu Ihren Bedürfnissen passt - dies zu ignorieren ist keine "unoptimierte" Programmierung, sondern "schlechte" Programmierung)

Es macht mich wahnsinnig, wenn ich solche Fragen höre, aber dann vergesse ich, dass jeder erst einmal alle Regeln selbst lernen muss, bevor er sie wirklich versteht. Sie werden es verstehen, nachdem Sie einen Monat damit verbracht haben, etwas zu debuggen, das jemand "optimiert" hat.

EDITAR:

In diesem Thread ist etwas Interessantes passiert. Sehen Sie sich die Antwort Nr. 1 an, sie ist ein Beispiel dafür, wie leistungsfähig der Compiler bei der Optimierung von Dingen ist. Der Test ist völlig ungültig, weil die nicht-reflektierende Instanziierung komplett herausgerechnet werden kann.

Lektion? Optimieren Sie NIEMALS, bevor Sie nicht eine saubere, ordentlich kodierte Lösung geschrieben und bewiesen haben, dass sie zu langsam ist.

34 Stimmen

Ich stimme dieser Antwort voll und ganz zu, aber wenn man vor einer wichtigen Designentscheidung steht, ist es hilfreich, eine Vorstellung von der Leistung zu haben, damit man sich nicht auf einen völlig untauglichen Weg begibt. Vielleicht geht er nur seiner Sorgfaltspflicht nach?

29 Stimmen

-1: Vermeiden, Dinge falsch zu machen, ist keine Optimierung, es ist einfach, Dinge zu tun. Optimierung bedeutet, Dinge auf die falsche, komplizierte Art und Weise zu tun, weil man sich entweder echte oder eingebildete Leistungsprobleme einhandelt.

5 Stimmen

@soru stimmt völlig zu. Eine verknüpfte Liste einer Array-Liste für eine Einfügesortierung vorzuziehen, ist einfach der richtige Weg, die Dinge zu tun. Aber diese spezielle Frage - es gibt gute Anwendungsfälle für beide Seiten der ursprünglichen Frage, so dass es falsch wäre, sich für eine Lösung zu entscheiden, die auf der Leistung basiert und nicht auf der brauchbarsten Lösung. Ich bin mir nicht sicher, ob wir uns überhaupt nicht einig sind, daher bin ich mir nicht sicher, warum Sie "-1" gesagt haben.

42voto

Peter Lawrey Punkte 511323

Sie können feststellen, dass A a = new A() von der JVM optimiert wird. Wenn Sie die Objekte in ein Array packen, schneiden sie nicht so gut ab ;) Der folgende Ausdruck...

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 Rechner etwa 150 ns beträgt.

0 Stimmen

Sie haben also gerade den Optimierer ausgeschaltet, so dass jetzt beide Versionen langsam sind. Die Reflexion ist also immer noch verdammt langsam.

14 Stimmen

@gbjbaanb wenn der Optimierer die Erstellung selbst optimiert hat, dann war es kein gültiger Test. Der Test von @Peter ist deshalb gültig, weil er tatsächlich die Erstellungszeiten vergleicht (der Optimierer könnte in KEINER realen Situation funktionieren, weil man in jeder realen Situation die Objekte braucht, die man instanziert).

10 Stimmen

@nes1983 In diesem Fall hätten Sie die Gelegenheit nutzen können, um eine bessere Benchmark zu erstellen. Vielleicht können Sie etwas Konstruktives vorschlagen, z. B. was im Hauptteil der Methode stehen sollte.

33voto

Esko Luontola Punkte 71758

Wenn es realmente schneller sein muss als Reflexion und es sich nicht nur um eine verfrühte Optimierung handelt, dann ist die Bytecode-Erzeugung mit ASM oder eine übergeordnete Bibliothek ist eine Option. Die Erzeugung des Bytecodes ist beim ersten Mal langsamer als die Verwendung von Reflection, aber sobald der Bytecode erzeugt wurde, ist er so schnell wie normaler Java-Code und wird vom JIT-Compiler optimiert.

Einige Beispiele für Anwendungen, die die Codegenerierung nutzen:

  • Aufrufen von Methoden auf Proxys, die von CGLIB ist etwas schneller als Javas dynamische Proxys da CGLIB Bytecode für seine Proxys erzeugt, dynamische Proxys aber nur Reflection ( Ich habe gemessen CGLIB ist bei Methodenaufrufen etwa 10x schneller, aber die Erstellung der Proxies war langsamer).

  • JSerial generiert Bytecode zum Lesen/Schreiben der Felder serialisierter Objekte, anstatt Reflection zu verwenden. Es gibt einige Benchmarks auf der Website von JSerial.

  • Ich bin mir nicht 100%ig sicher (und ich habe jetzt keine Lust, die Quelle zu lesen), aber ich glaube Guice erzeugt Bytecode für die Injektion von Abhängigkeiten. Korrigieren Sie mich, wenn ich falsch liege.

29voto

kdgregory Punkte 37614

"Signifikant" ist völlig kontextabhängig.

Wenn Sie Reflection verwenden, um ein einzelnes Handler-Objekt auf der Grundlage einer Konfigurationsdatei zu erstellen, und dann den Rest Ihrer Zeit mit der Ausführung von Datenbankabfragen verbringen, dann ist das unbedeutend. Wenn Sie eine große Anzahl von Objekten über Reflection in einer engen Schleife erstellen, dann ja, es ist signifikant.

Im Allgemeinen sollte die Flexibilität des Entwurfs (wo nötig!) den Einsatz der Reflexion bestimmen, nicht die Leistung. Um jedoch festzustellen, ob die Leistung ein Problem darstellt, müssen Sie ein Profil erstellen, anstatt willkürliche Antworten aus einem Diskussionsforum zu erhalten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X