it-swarm.com.de

Java Reflection Performance

Führt das Erstellen eines Objekts mithilfe von Reflektion anstelle des Aufrufs des Klassenkonstruktors zu signifikanten Leistungsunterschieden?

162
dmanxiii

Ja - absolut. Das Nachschlagen einer Klasse durch Reflektion ist, um die Größenordnung, teurer.

Zitat Javas Dokumentation zum Nachdenken :

Da es sich bei der Reflektion um Typen handelt, die dynamisch aufgelöst werden, können bestimmte Java Optimierungen der virtuellen Maschine nicht durchgeführt werden. Folglich sind Reflektionsvorgänge langsamer als ihre nicht reflektierenden Gegenstücke und sollten in Codeabschnitten vermieden werden die in leistungskritischen Anwendungen häufig aufgerufen werden.

Hier ist ein einfacher Test, den ich auf meinem Computer mit Sun JRE 6u10 in 5 Minuten 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 überarbeitet werden, dies ist jedoch nur ein einfaches Beispiel.

Selbst wenn Sie nur instanziieren, erhalten Sie immer noch einen Performance-Hit:

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

Wieder YMMV.

163
Yuval Adam

Ja, es ist langsamer.

Aber denken Sie an die verdammte Regel Nr. 1 - PREMATURE OPTIMIZATION IS THE ROOT OF ALL EVIL

(Nun, kann mit # 1 für DRY gebunden werden)

Ich schwöre, wenn jemand bei der Arbeit auf mich zukommt und mich fragt, ob ich in den nächsten Monaten sehr wachsam bin.

Sie dürfen niemals optimieren, bis Sie sicher sind, dass Sie es brauchen. Schreiben Sie bis dahin einfach guten, lesbaren Code.

Oh, und ich meine auch nicht, dummen Code zu schreiben. Überlegen Sie sich nur, wie Sie es am saubersten machen 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 - Ignorieren dieser ist keine "unoptimierte" Programmierung , es ist "schlechte" Programmierung

Es macht mich verrückt, wenn ich solche Fragen höre, aber dann vergesse ich, dass jeder alle Regeln selbst lernen muss, bevor er sie wirklich versteht. Sie erhalten es, nachdem Sie einen Mann-Monat damit verbracht haben, etwas zu debuggen, das jemand "optimiert" hat.

BEARBEITEN:

In diesem Thread ist etwas Interessantes passiert. Überprüfen Sie die Antwort 1. Sie 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 ausgeschlossen werden kann.

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

83
Bill K

Möglicherweise stellen Sie fest, dass A a = new A() von der JVM optimiert wird. Wenn Sie die Objekte in ein Array einfügen, ist ihre Leistung nicht so gut.;) Die folgenden Ausdrucke ...

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 ungefähr 150 ns beträgt.

36
Peter Lawrey

Wenn es wirklich für etwas Schnelleres als Nachdenken gibt, und es nicht nur eine vorzeitige Optimierung ist, dann wird die Bytecode-Erzeugung mit [~ # ~] asm [~ # ~] oder eine übergeordnete Bibliothek ist eine Option. Das erstmalige Generieren des Bytecodes ist langsamer als das reine Verwenden von Reflection, aber sobald der Bytecode generiert wurde, ist er so schnell wie normal Java Code und wird vom JIT-Compiler optimiert.

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

  • Das Aufrufen von Methoden für Proxys, die von [~ # ~] cglib [~ # ~] generiert wurden, ist etwas schneller als Javas dynamische Proxys , da CGLIB Bytecode für seine Proxys generiert Dynamische Proxys verwenden jedoch nur Reflektion ( Ich habe gemessen CGLIB, um in Methodenaufrufen etwa 10-mal schneller zu sein, aber das Erstellen der Proxys war langsamer).

  • JSerial generiert Bytecode zum Lesen/Schreiben der Felder von serialisierten Objekten, anstatt Reflektion zu verwenden. Es gibt einige Benchmarks auf der Website von JSerial.

  • Ich bin nicht 100% sicher (und ich habe keine Lust, den Quellcode zu lesen), aber ich denke, dass Guice Bytecode generiert, um die Abhängigkeitsinjektion durchzuführen. Korrigiere mich, wenn ich falsch liege.

26
Esko Luontola

"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 Reflektion in einer engen Schleife erstellen, ist dies von Bedeutung.

Im Allgemeinen sollte die Flexibilität des Designs (wo erforderlich!) Die Verwendung von Reflektion 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.

25
kdgregory

Es gibt einige Überlegungen, aber es ist auf modernen VMs viel kleiner als früher.

Wenn Sie Reflection verwenden, um jedes einfache Objekt in Ihrem Programm zu erstellen, stimmt etwas nicht. Die gelegentliche Verwendung sollte, wenn Sie einen guten Grund haben, überhaupt kein Problem darstellen.

23
Marcus Downing

Die Reflexion ist langsam, obwohl die Objektzuweisung nicht so aussichtslos ist wie andere Aspekte der Reflexion. Um eine gleichwertige Leistung mit reflexionsbasierter Instanziierung zu erzielen, müssen Sie Ihren Code schreiben, damit das JIT erkennen kann, welche Klasse instanziiert wird. Wenn die Identität der Klasse nicht bestimmt 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 möglicherweise dynamisch bestimmen, welche Klasse vorherrscht, und für diese Klasse optimieren.

Beachten Sie, dass die Mikrobenchmarks in diesem Thread stark fehlerhaft sind. Nehmen Sie sie daher mit einem Körnchen Salz. Das mit Abstand am wenigsten fehlerhafte ist das von Peter Lawrey: Es führt Aufwärmläufe durch, um die Methoden zu ignorieren, und es besiegt (bewusst) die Escape-Analyse, um sicherzustellen, dass die Zuweisungen tatsächlich stattfinden. Sogar das hat seine Probleme: Es kann beispielsweise erwartet werden, dass die enorme Anzahl von Array-Speichern Caches und Speicherpuffer zerstört, sodass dies meist ein Speicherbenchmark ist, wenn Ihre Zuweisungen sehr schnell sind. (Ein großes Lob an Peter, der zu dem Schluss gekommen ist, dass der Unterschied eher "150ns" als "2.5x" ist. Ich vermute, er macht so etwas seinen Lebensunterhalt.)

7
Doradus

Ja, bei der Verwendung von Reflection tritt ein Leistungseinbruch auf, 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] Aufruf der Methode 1000000 mal reflexiv mit Lookup dauerte 5618 Millis

[Java] Aufruf der Methode 1000000 mal reflexartig mit Cache dauerte 270 Millis

7
mel3kings

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, mussten wir diesen Code umgestalten, um keine Reflektion zu verwenden. Wenn Sie wissen, was die Klasse ist, rufen Sie einfach den Konstruktor direkt auf.

6
Elie

Interessanterweise führt die Einstellung 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
6

In doReflection () ist der Overhead aufgrund von Class.forName ("misc.A") (dies würde eine Klassensuche erfordern, die möglicherweise den Klassenpfad auf dem Dateisystem durchsucht) und nicht aufgrund der für die Klasse aufgerufenen 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 wirklich für jeden Aufruf der Schleife ausgeführt werden.

4
tikoo

Ja, es wird immer langsamer sein, ein Objekt durch Reflektion zu erstellen, da die JVM den Code bei der Kompilierung 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");
    }
}
1
aledbf

Oft können Sie Apache-Commons BeanUtils oder PropertyUtils verwenden, um Informationen zu erhalten (im Grunde genommen werden die Metadaten zu den Klassen zwischengespeichert, sodass nicht immer Reflektion erforderlich ist).

1
sproketboy

Ich denke, es hängt davon ab, wie leicht/schwer die Zielmethode ist. Wenn die Zielmethode sehr leicht ist (z. B. Getter/Setter), könnte sie 1-3 mal langsamer sein. Wenn die Zielmethode mindestens 1 Millisekunde dauert, ist die Leistung sehr ähnlich. Hier ist der Test, den ich mit Java 8 und reflectasm gemacht 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 bei GitHub verfügbar: ReflectionTest.Java

0
user_3380739