50 votes

Pourquoi les annotations sous Android posent-elles un problème de performances (lent)?

Je suis l'auteur principal de ORMLite qui utilise des annotations Java sur les classes de construire des schémas de base de données. Un gros problème de performances de démarrage pour notre forfait s'avère être l'appel de l'annotation méthodes sous Android 1.6. Je vois le même comportement jusqu'à 3.0.

Nous constatons que le simple suivant l'annotation de code est incroyablement GC intensif et un réel problème de performance. 1000 appels à une annotation méthode prend près d'une seconde sur un rapide de la boîte. Le même code fonctionne sur mon Mac Pro 28 millions de dollars (sic) appels en même temps. Nous avons une annotation qui a 25 méthodes et nous aimerions faire plus de 50 de ces une seconde.

Personne ne sait pourquoi ce qui se passe et si il n'y a aucune contourner? Il y a certainement des choses que ORMLite peut faire en termes de mise en cache de cette information, mais il n'y a rien que nous pouvons faire pour "réparer" les annotations sous Android? Merci.

public void testAndroidAnnotations() throws Exception {
    Field field = Foo.class.getDeclaredField("field");
    MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
    long before = System.currentTimeMillis();
    for (int i = 0; i < 1000; i++)
        myAnnotation.foo();
    Log.i("test", "in " + (System.currentTimeMillis() - before) + "ms");
}
@Target(FIELD) @Retention(RUNTIME)
private static @interface MyAnnotation {
    String foo();
}
private static class Foo {
    @MyAnnotation(foo = "bar")
    String field;
}

Cette résultats dans la suite de la sortie du journal:

I/TestRunner(  895): started: testAndroidAnnotations
D/dalvikvm(  895): GC freed 6567 objects / 476320 bytes in 85ms
D/dalvikvm(  895): GC freed 8951 objects / 599944 bytes in 71ms
D/dalvikvm(  895): GC freed 7721 objects / 524576 bytes in 68ms
D/dalvikvm(  895): GC freed 7709 objects / 523448 bytes in 73ms
I/test    (  895): in 854ms

EDIT:

Après @candrews m'a orienté dans la bonne direction, j'ai fait un peu de fouiller le code. Le problème semble être causé par quelque terrible, brut code en Method.equals(). C'est l'appel de la toString() des deux méthodes et de les comparer. Chaque toString() utilisation StringBuilder avec un tas d'ajouter des méthodes sans une bonne initialisation de la taille. Faire l' .equals en comparant les champs seraient nettement plus rapide.

EDIT:

Une réflexion intéressante d'amélioration de la performance m'a été donné. Nous sommes maintenant à l'aide de la réflexion de jeter un regard à l'intérieur de l' AnnotationFactory classe de lire la liste des champs directement. Cela rend la réflexion de la classe 20 fois plus rapide pour nous, car il évite de l'appeler, qui est l'aide de l' method.equals() appel. Ce n'est pas une solution générique, mais voici le code Java à partir de ORMLite dépôt SVN. Pour une solution générique, voir yanchenko la réponse ci-dessous.

22voto

candrews Points 1316

Google a reconnu le problème et l'a corrigé "après Honeycomb"

https://code.google.com/p/android/issues/detail?id=7811

Donc, au moins, ils sont au courant et l’ont supposément corrigé pour une version future.

6voto

yanchenko Points 24142

Voici une version générique de Gris's & user931366l'idée:

public class AnnotationElementsReader {

    private static Field elementsField;
    private static Field nameField;
    private static Method validateValueMethod;

    public static HashMap<String, Object> getElements(Annotation annotation)
            throws Exception {
        HashMap<String, Object> map = new HashMap<String, Object>();
        InvocationHandler handler = Proxy.getInvocationHandler(annotation);
        if (elementsField == null) {
            elementsField = handler.getClass().getDeclaredField("elements");
            elementsField.setAccessible(true);
        }
        Object[] annotationMembers = (Object[]) elementsField.get(handler);
        for (Object annotationMember : annotationMembers) {
            if (nameField == null) {
                Class<?> cl = annotationMember.getClass();
                nameField = cl.getDeclaredField("name");
                nameField.setAccessible(true);
                validateValueMethod = cl.getDeclaredMethod("validateValue");
                validateValueMethod.setAccessible(true);
            }
            String name = (String) nameField.get(annotationMember);
            Object val = validateValueMethod.invoke(annotationMember);
            map.put(name, val);
        }
        return map;
    }

}

J'ai comparé une annotation avec les 4 éléments.
Milliseconde fois pour 10000 itérations soit de l'obtention de valeurs de toutes ou de l'appel de la méthode ci-dessus:

     Device        Default  Hack
HTC Desire 2.3.7    11094   730
Emulator 4.0.4      3157    528
Galaxy Nexus 4.3    1248    392

Voici comment j'ai intégré dans DroidParts: https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69.

5voto

user931366 Points 469

À suivre sur ce, il y a toujours un problème lors de l'appel de méthodes sur les annotations. Le bug énumérés ci-dessus par candrews résout le getAnnotation() la lenteur, mais l'appel d'une méthode sur l'annotation est toujours un problème en raison de la Méthode.equals ().

Ne pouvais pas trouver un rapport de bug pour la Méthode.equals() j'ai donc créé un ici: https://code.google.com/p/android/issues/detail?id=37380

Edit: Donc, mon travail autour de cela (merci pour les idées @Gris), est en fait assez simple. (c'est trunkcated code, certains de la mise en cache et telle est omis)

annotationFactory = Class.forName("org.apache.harmony.lang.annotation.AnnotationFactory");
getElementDesc = annotationFactory.getMethod("getElementsDescription", Class.class);
Object[] members = (Object[])getElementDesc.invoke(annotationFactory, clz); // these are AnnotationMember[]

Object element = null;
for (Object e:members){ // AnnotationMembers
    Field f = e.getClass().getDeclaredField("name");
    f.setAccessible(true);
    String fname = (String) f.get(e);
    if (methodName.equals(fname)){
        element = e;
    break;
    }
}

if (element == null) throw new Exception("Element was not found");
Method m = element.getClass().getMethod("validateValue");
return m.invoke(element, args);

Vous kilométrage peut varier en fonction de l'utilisation, mais en mai, il est d'environ 15 à 20 fois plus rapide que de faire le "droit chemin"

1voto

Mister Smith Points 8587

Je pense que si vous parvenez à modifier la stratégie de rétention RUNTIME, elle ne devrait pas être aussi lente.

EDIT: Je sais, pour votre projet, cela peut ne pas être une option. Peut-être est-ce plus un problème de ce que vous faites avec cette annotation que de mauvaises performances en général.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X