Java Reflection-prestaties

Resulteert het maken van een object met reflectie in plaats van het aanroepen van de klassenconstructor in significante prestatieverschillen?


Antwoord 1, autoriteit 100%

Ja – absoluut.Een klas opzoeken via reflectie is in grootteduurder.

Citeer Java’s documentatie over reflectie:

Omdat reflectie typen omvat die dynamisch worden opgelost, kunnen bepaalde optimalisaties van Java-virtuele machines niet worden uitgevoerd. Bijgevolg presteren reflectieve bewerkingen langzamer dan hun niet-reflecterende tegenhangers en moeten ze worden vermeden in codesecties die vaak worden aangeroepen in prestatiegevoelige toepassingen.

Hier is een eenvoudige test die ik in 5 minuten heb gehackt op mijn computer, met 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);
    }
}

Met deze resultaten:

35 // no reflection
465 // using reflection

Houd er rekening mee dat de lookup en de instantiatie samen worden gedaan, en in sommige gevallen kan de lookup worden weggewerkt, maar dit is slechts een eenvoudig voorbeeld.

Zelfs als je alleen instantiëert, krijg je nog steeds een prestatiehit:

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

Nogmaals, YMMV.


Antwoord 2, autoriteit 52%

Ja, het is langzamer.

Maar onthoud de verdomde #1 regel: VOORTIJDIGE OPTIMALISATIE IS DE WORTEL VAN ALLE KWAAD

(Nou, misschien gelijk met #1 voor DROOG)

Ik zweer het, als iemand op mijn werk naar me toe zou komen en me dit zou vragen, zou ik de komende maanden erg op hun code letten.

Je moet nooit optimaliseren totdat je zeker weet dat je het nodig hebt, schrijf tot die tijd gewoon goede, leesbare code.

O, en ik bedoel ook niet domme code schrijven. Denk gewoon na over de schoonste manier waarop u het kunt doen – niet kopiëren en plakken, enz. (Wees toch op uw hoede voor dingen zoals innerlijke lussen en het gebruik van de verzameling die het beste bij u past – dit negeren is geen “niet-geoptimaliseerde” programmering , het is “slechte” programmering)

Ik word gek als ik dit soort vragen hoor, maar dan vergeet ik dat iedereen eerst alle regels zelf moet leren voordat ze het echt snappen. Je krijgt het nadat je een manmaand hebt besteed aan het opsporen van fouten in iets dat iemand heeft ‘geoptimaliseerd’.

BEWERKEN:

Er is iets interessants gebeurd in deze thread. Controleer het #1-antwoord, het is een voorbeeld van hoe krachtig de compiler is in het optimaliseren van dingen. De test is volledig ongeldig omdat de niet-reflecterende instantiatie volledig kan worden weggelaten.

Les? Optimaliseer NOOIT totdat je een schone, netjes gecodeerde oplossing hebt geschreven en hebt bewezen dat deze te traag is.


Antwoord 3, autoriteit 20%

Het kan zijn dat A a = nieuwe A() wordt geoptimaliseerd door de JVM.
Als je de objecten in een array plaatst, presteren ze niet zo goed. 😉
De volgende afdrukken…

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);
    }
}

Dit suggereert dat het verschil ongeveer 150 ns is op mijn machine.


Antwoord 4, autoriteit 17%

Als er echtbehoefte is aan iets dat sneller is dan reflectie, en het is niet alleen een voortijdige optimalisatie, dan is het genereren van bytecode met ASMof een bibliotheek op een hoger niveau is een optie. De eerste keer de bytecode genereren is langzamer dan alleen reflectie gebruiken, maar zodra de bytecode is gegenereerd, is deze net zo snel als normale Java-code en wordt deze geoptimaliseerd door de JIT-compiler.

Enkele voorbeelden van applicaties die codegeneratie gebruiken:

  • Het aanroepen van methoden op proxy’s die zijn gegenereerd door CGLIBis iets sneller dan Java’s dynamische proxy’s, omdat CGLIB bytecode genereert voor zijn proxy’s, maar dynamische proxy’s gebruiken alleen reflectie (Ik heb gemetenCGLIB ongeveer 10x sneller in methode oproepen, maar het maken van de proxy’s ging langzamer).

  • JSerialgenereert bytecode voor het lezen/schrijven van de velden van geserialiseerde objecten, in plaats van reflectie te gebruiken. Er zijn enkele benchmarksop de site van JSerial.

  • Ik weet het niet 100% zeker (en ik heb nu geen zin om de bron te lezen), maar ik denk Guicegenereert bytecode om afhankelijkheidsinjectie uit te voeren. Corrigeer me als ik het mis heb.


Antwoord 5, autoriteit 15%

“Belangrijk” is volledig afhankelijk van de context.

Als je reflectie gebruikt om een ​​object met één handler te maken op basis van een configuratiebestand, en de rest van je tijd besteedt aan het uitvoeren van databasequery’s, dan is dat onbelangrijk. Als je grote aantallen objecten creëert via reflectie in een strakke lus, dan is dat inderdaad belangrijk.

Over het algemeen zou ontwerpflexibiliteit (waar nodig!) uw gebruik van reflectie moeten stimuleren, niet van prestaties. Als u echter wilt bepalen of prestaties een probleem zijn, moet u zich profileren in plaats van willekeurige reacties van een discussieforum te krijgen.


Antwoord 6, autoriteit 12%

Er is wat overhead met reflectie, maar het is een stuk kleiner op moderne VM’s dan vroeger.

Als je reflectie gebruikt om elk eenvoudig object in je programma te maken, dan is er iets mis. Af en toe gebruiken, als je daar een goede reden voor hebt, zou helemaal geen probleem moeten zijn.


Antwoord 7, autoriteit 7%

Ja, er is een prestatiehit bij het gebruik van Reflection, maar een mogelijke oplossing voor optimalisatie is het cachen van de 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");

zal resulteren in:

[java] Aanroepmethode 1000000 keer reflexief met opzoeken kostte 5618 milliliter

[java] Aanroepmethode 1000000 keer reflexief met cache kostte 270 milliliter


Antwoord 8, autoriteit 5%

Interessant genoeg leidt het instellen van setAccessible(true), waarbij de beveiligingscontroles worden overgeslagen, tot een kostenbesparing van 20%.

Zonder setAccessible(true)

new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns

Met setAccessible(true)

new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns

Antwoord 9, autoriteit 4%

Reflectie is traag, hoewel objecttoewijzing niet zo hopeloos is als andere aspecten van reflectie. Om gelijkwaardige prestaties te bereiken met op reflectie gebaseerde instantiatie, moet u uw code schrijven zodat de jit kan zien welke klasse wordt geïnstantieerd. Als de identiteit van de klasse niet kan worden bepaald, kan de toewijzingscode niet worden inline. Erger nog, ontsnappingsanalyse mislukt en het object kan niet worden gestapeld. Als je geluk hebt, kan de runtime-profilering van de JVM je te hulp komen als deze code hot wordt, en dynamisch bepalen welke klasse de overhand heeft en voor die klasse kan worden geoptimaliseerd.

Houd er rekening mee dat de microbenchmarks in deze thread zeer gebrekkig zijn, dus neem ze met een korreltje zout. Verreweg de minst gebrekkige is die van Peter Lawrey: het voert warming-upruns uit om de methoden op scherp te zetten, en het verslaat (bewust) ontsnappingsanalyses om ervoor te zorgen dat de toewijzingen daadwerkelijk plaatsvinden. Zelfs dat heeft echter zijn problemen: het enorme aantal array-stores kan bijvoorbeeld worden verwacht om caches en opslagbuffers te verslaan, dus dit zal uiteindelijk een geheugenbenchmark worden als je toewijzingen erg snel zijn. (Een pluim voor Peter voor de juiste conclusie: het verschil is “150ns” in plaats van “2,5x”. Ik vermoed dat hij dit soort dingen doet voor de kost.)


Antwoord 10, autoriteit 4%

Ja, het is aanzienlijk langzamer. We voerden een code uit die dat deed, en hoewel ik op dit moment niet over de statistieken beschik, was het eindresultaat dat we die code moesten herstructureren om reflectie niet te gebruiken. Als je weet wat de klasse is, bel dan rechtstreeks de constructor.


Antwoord 11, autoriteit 2%

In de doReflection() is de overhead vanwege Class.forName(“misc.A”) (waarvoor een class-lookup nodig zou zijn, mogelijk het klassenpad op het bestandssysteem scannen), in plaats van de newInstance() aangeroepen op de klas. Ik vraag me af hoe de statistieken eruit zouden zien als de Class.forName(“misc.A”) slechts eenmaal buiten de for-lus wordt gedaan, het hoeft niet echt voor elke aanroep van de lus te worden gedaan.


Antwoord 12

Ja, het zal altijd langzamer zijn om een ​​object te maken door reflectie, omdat de JVM de code niet kan optimaliseren tijdens de compilatie. Zie de Sun/Java Reflectie-tutorialsvoor meer details.

Zie deze eenvoudige 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");
    }
}

Other episodes