web-dev-qa-db-fra.com

Java Performances de Reflection

La création d'un objet utilisant la réflexion plutôt que d'appeler le constructeur de classe entraîne-t-elle des différences de performances significatives?

162
dmanxiii

Oui - absolument. Regarder une classe par réflexion est, par magnitude, plus cher.

Citant documentation de Java sur la réflexion :

Étant donné que la réflexion implique des types résolus dynamiquement, certaines Java optimisations de machines virtuelles Java) ne peuvent pas être effectuées. Par conséquent, les opérations réfléchies ont des performances plus lentes que leurs équivalents non réfléchissants et doivent être évitées dans des sections de code. qui sont appelés fréquemment dans les applications sensibles à la performance.

Voici un test simple que j'ai piraté en 5 minutes sur ma machine, sous 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);
    }
}

Avec ces résultats:

35 // no reflection
465 // using reflection

N'oubliez pas que la recherche et l'instanciation sont effectuées ensemble et que, dans certains cas, la recherche peut être refactorisée, mais il ne s'agit que d'un exemple élémentaire.

Même si vous instanciez simplement, vous obtenez toujours un coup dur en performance:

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

Encore une fois, YMMV.

163
Yuval Adam

Oui, c'est plus lent.

Mais souvenez-vous de la foutue règle n ° 1 - OPTIMISATION PRÉMATURÉE IS LA RACINE DE TOUT MAUVAIS

(Eh bien, peut être à égalité avec # 1 pour DRY)

Je jure que si quelqu'un venait à moi au travail et me demandait ceci, je serais très vigilant sur leur code pour les prochains mois.

Vous ne devez jamais optimiser tant que vous n'êtes pas sûr d'en avoir besoin. Jusque-là, écrivez simplement un code lisible et de qualité.

Oh, et je ne veux pas dire écrire un code stupide non plus. Il suffit de penser à la manière la plus propre possible: ne pas copier/coller, etc. , c’est une "mauvaise" programmation)

Cela me fait peur quand j'entends des questions comme celle-ci, mais j'oublie ensuite que tout le monde doit apprendre toutes les règles avant de les comprendre. Vous l'obtiendrez après avoir passé un mois-homme à déboguer quelque chose de quelqu'un "optimisé".

MODIFIER:

Une chose intéressante est arrivée dans ce fil. Vérifiez la réponse n ° 1, c’est un exemple de la puissance du compilateur pour optimiser les choses. Le test est totalement invalide car l’instanciation non-réfléchissante peut être complètement factorisée.

Leçon? N'optimisez JAMAIS tant que vous n'avez pas écrit une solution propre, soigneusement codée et prouvé qu'elle est trop lente.

83
Bill K

Vous constaterez peut-être que A a = new A() est optimisé par la machine virtuelle Java. Si vous placez les objets dans un tableau, ils ne fonctionneront pas aussi bien. ;) Les estampes suivantes ...

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

Cela suggère que la différence est d’environ 150 ns sur ma machine.

36
Peter Lawrey

S'il y a vraiment vraiment besoin de quelque chose de plus rapide que la réflexion, et qu'il ne s'agit pas simplement d'une optimisation prématurée, la génération de bytecodes avec [~ # ~] asm [~ # ~] ou une bibliothèque de niveau supérieur est une option. Générer le code secondaire la première fois est plus lent que d'utiliser uniquement la réflexion, mais une fois que le code secondaire a été généré, il est aussi rapide que la normale Java) et sera optimisé par le compilateur JIT.

Quelques exemples d'applications utilisant la génération de code:

  • L'appel de méthodes sur les procurations générées par [~ # ~] cglib [~ # ~] est légèrement plus rapide que celui de Java procurations dynamiques , car CGLIB génère du bytecode pour ses proxies , mais les proxys dynamiques utilisent uniquement la réflexion ( j’ai mesuré CGLIB pour être environ 10 fois plus rapide dans les appels de méthode, mais la création des proxies a été plus lente).

  • JSerial génère un bytecode pour la lecture/écriture des champs d'objets sérialisés, au lieu d'utiliser la réflexion. Il y a des points de repère sur le site de JSerial.

  • Je ne suis pas sûr à 100% (et je n'ai pas envie de lire le code source à présent), mais je pense que Guice génère du bytecode pour effectuer une injection de dépendance. Corrige moi si je me trompe.

26
Esko Luontola

"Significatif" dépend entièrement du contexte.

Si vous utilisez la réflexion pour créer un seul objet gestionnaire basé sur un fichier de configuration, puis que vous passez le reste de votre temps à exécuter des requêtes de base de données, cela n'a pas d'importance. Si vous créez un grand nombre d'objets par réflexion en boucle étroite, alors oui, c'est important.

En général, la flexibilité de la conception (le cas échéant!) Devrait motiver votre utilisation de la réflexion et non de la performance. Cependant, pour déterminer si les performances sont un problème, vous devez profiler plutôt que de recevoir des réponses arbitraires d'un forum de discussion.

25
kdgregory

La réflexion comporte des frais généraux, mais elle est beaucoup moins importante qu’auparavant sur les machines virtuelles modernes.

Si vous utilisez la réflexion pour créer tous les objets simples de votre programme, alors quelque chose ne va pas. L'utiliser occasionnellement, lorsque vous avez de bonnes raisons, ne devrait pas être un problème du tout.

23
Marcus Downing

La réflexion est lente, bien que l'attribution d'objet ne soit pas aussi sans espoir que d'autres aspects de la réflexion. Pour obtenir des performances équivalentes avec une instanciation basée sur la réflexion, vous devez écrire votre code afin que le script puisse déterminer quelle classe est instanciée. Si l'identité de la classe ne peut pas être déterminée, le code d'attribution ne peut pas être en ligne. Pire encore, l'analyse d'échappement échoue et l'objet ne peut pas être alloué en pile. Si vous avez de la chance, le profilage au moment de l'exécution de la machine virtuelle Java peut venir à la rescousse si ce code chauffe, et peut déterminer de manière dynamique la classe qui prédomine et peut l'optimiser pour celui-là.

Sachez que les micro-repères de ce fil sont profondément défectueux, alors prenez-les avec un grain de sel. Le moins imparfait, et de loin, est Peter Lawrey: il effectue des échauffements pour écarter les méthodes et renverse (consciemment) l'analyse d'évasion pour s'assurer que les affectations ont bien lieu. Même celui-ci a ses problèmes, cependant: par exemple, on peut s’attendre à ce que le nombre considérable de magasins de modules RAID supprime les caches et les mémoires tampons, de sorte que cela deviendra essentiellement un repère de mémoire si vos allocations sont très rapides. (Félicitations à Peter pour avoir bien compris la conclusion: que la différence est "150ns" plutôt que "2.5x". Je suppose qu'il fait ce genre de chose pour gagner sa vie.)

7
Doradus

Oui, il y a un impact négatif sur les performances lors de l'utilisation de Reflection, mais une solution de contournement possible pour l'optimisation est la mise en cache de la méthode:

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

aura pour résultat:

La méthode d'appel [Java] 1000000 fois par réflexe avec recherche a pris 5618 millis

[Java] La méthode d'appel 1000000 fois par réflexe avec le cache a pris 270 millis

7
mel3kings

Oui, c'est beaucoup plus lent. Nous utilisions du code qui faisait cela, et bien que je ne dispose pas des métriques disponibles pour le moment, le résultat final a été que nous avons dû refactoriser ce code pour ne pas utiliser la réflexion. Si vous connaissez la classe, appelez directement le constructeur.

6
Elie

Il est intéressant de noter que la définition de setAccessible (true), qui ignore les contrôles de sécurité, entraîne une réduction des coûts de 20%.

Sans setAccessible (true)

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

Avec setAccessible (true)

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

Dans doReflection (), le surdébit est dû à Class.forName ("misc.A") (qui nécessiterait une recherche de classe, balayant potentiellement le chemin de classe sur le système de fils), plutôt que la newInstance () appelée sur la classe. Je me demande à quoi ressemblent les statistiques si le Class.forName ("misc.A") est effectué une seule fois en dehors de la boucle for, il n'est pas nécessaire de le faire pour chaque invocation de la boucle.

4
tikoo

Oui, il sera toujours plus lent de créer un objet par réflexion car la JVM ne peut pas optimiser le code lors de la compilation. Voir les Sun/Java tutoriels de réflexion pour plus de détails.

Voir ce test simple:

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

Vous pouvez souvent utiliser Apache commons BeanUtils ou PropertyUtils pour effectuer une introspection (ils mettent en cache les métadonnées sur les classes afin qu’ils n’aient pas toujours besoin d’utiliser la réflexion).

1
sproketboy

Je pense que cela dépend de la légèreté/de la lourdeur de la méthode cible. si la méthode cible est très légère (getter/setter, par exemple), elle pourrait être 1 à 3 fois plus lente. si la méthode cible prend environ 1 milliseconde ou plus, les performances seront très proches. voici le test que j'ai fait avec Java 8 et reflectasm :

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

Le code de test complet est disponible sur GitHub: ReflectionTest.Java

0
user_3380739