Warum sind Anmerkungen unter Android ein solches Leistungsproblem (langsam)?

Lesezeit: 6 Minuten

Benutzer-Avatar
Grau

Ich bin der Hauptautor von ORMLite das Java-Annotationen für Klassen verwendet, um Datenbankschemata zu erstellen. Als großes Startup-Performance-Problem für unser Paket entpuppt sich der Aufruf von Annotationsmethoden unter Android 1.6. Ich sehe das gleiche Verhalten bis 3.0.

Wir sehen, dass der folgende einfache Anmerkungscode ist unglaublich GC-intensiv und ein echtes Leistungsproblem. 1000 Aufrufe einer Annotationsmethode dauern auf einem schnellen Android-Gerät fast eine Sekunde. Der gleiche Code, der auf meinem Macbook Pro läuft, kann 28 Millionen (sic) Aufrufe in der gleichen Zeit ausführen. Wir haben eine Anmerkung, die 25 Methoden enthält, und wir möchten mehr als 50 davon pro Sekunde ausführen.

Weiß jemand warum das passiert und ob es Abhilfe gibt? Es gibt sicherlich Dinge, die ORMLite in Bezug auf das Zwischenspeichern dieser Informationen tun kann, aber gibt es irgendetwas, was wir tun können, um Anmerkungen unter Android zu “reparieren”? Vielen Dank.

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

Dies führt zu folgender Protokollausgabe:

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

BEARBEITEN:

Nachdem @candrews mich in die richtige Richtung gelenkt hatte, habe ich etwas im Code herumgestochert. Das Leistungsproblem scheint durch einen schrecklichen, groben Code in verursacht worden zu sein Method.equals(). Es ruft die toString() beider Methoden und vergleicht sie dann. Jeder toString() verwenden StringBuilder mit einer Reihe von Append-Methoden ohne eine gute Initialisierungsgröße. Das tun .equals durch Vergleichen von Feldern wäre deutlich schneller.

BEARBEITEN:

Eine interessante Verbesserung der Reflexionsleistung wurde mir gegeben. Wir verwenden jetzt Reflektion, um einen Blick in das Innere zu werfen AnnotationFactory Klasse, um die Liste der Felder direkt zu lesen. Dies macht die Reflexionsklasse 20 mal schneller für uns, da es den Aufruf umgeht, der die verwendet method.equals() Anruf. Es ist keine generische Lösung, aber hier ist der Java-Code von ORMLite-SVN-Repository. Eine generische Lösung finden Sie in der Antwort von Yanchenko unten.

  • Sehen Sie ähnliche Timings, wenn Sie eine verwenden int eher als ein String für dein foo Eigentum? Vielleicht ist es ein Problem mit dem String-Pool?

    – nikolaus.hauskind

    14. September 2011 um 14:00 Uhr


  • Gleichzeitig mit int oder jede andere Art. Hier geht es um die Anmerkungen und nicht um das, was sie kommentieren. Tnx.

    – Grau

    14. September 2011 um 14:57 Uhr

  • @Gray Gemäß dem Problem, auf das candrews verwiesen hat, und dem Problem, auf das in den Kommentaren dieser Antwort verwiesen wird, wurde dies behoben. Wissen Sie, welche Android-Version Sie sicher löschen können? ORMLite config file?

    – das Blang

    9. Februar 2015 um 16:07 Uhr

Benutzer-Avatar
Kerzen

Google hat das Problem erkannt und „post-Honeycomb“ behoben

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

Zumindest wissen sie davon und haben es angeblich für eine zukünftige Version behoben.

  • Danke dafür @candrews. Ich bin mir nicht 100% sicher, dass das von Ihnen aufgelistete Problem speziell schuld ist, aber es ist sicherlich nah dran. Sieht aus wie Method.equals() ist der wahre Übeltäter. Ich habe den Fehler kommentiert.

    – Grau

    14. September 2011 um 15:29 Uhr

  • Ich habe gerade die Bestätigung erhalten, dass das Problem tatsächlich schuld ist. Nochmals vielen Dank @candrews.

    – Grau

    14. September 2011 um 18:22 Uhr

  • Ich denke, es gibt noch große Probleme. Ich habe gerade eingereicht code.google.com/p/android/issues/detail?id=43827

    – Jonathan Perlow

    31. Januar 2013 um 20:18 Uhr

Benutzer-Avatar
Yanchenko

Hier ist eine generische Version von Grau‘s & Benutzer931366‘Seite A:

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

}

Ich habe eine Anmerkung mit 4 Elementen bewertet.
Millisekundenzeiten für 10000 Iterationen, um entweder Werte von allen zu erhalten oder die obige Methode aufzurufen:

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

Hier ist, wie ich es integriert habe DroidParts: https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69.

  • +1 Gute generische Lösung. Ich bin ein wenig überrascht, dass Sie mir (oder ORMLite) die Lösung im Code nicht angerechnet haben. Ich hätte.

    – Grau

    6. August 2013 um 14:20 Uhr


  • das hat mir unzählige Stunden gespart. Jeder in diesem Thread rockt!

    – spionieren

    8. Februar 2016 um 23:44 Uhr

Benutzer-Avatar
Benutzer931366

Um dies weiterzuverfolgen, gibt es hier immer noch ein Problem beim Aufrufen von Methoden für Anmerkungen. Der oben von candrews aufgeführte Fehler behebt die Langsamkeit von getAnnotation(), aber das Aufrufen einer Methode für die Anmerkung ist aufgrund der Probleme mit Method.equals() immer noch ein Problem.

Konnte keinen Fehlerbericht für Method.equals() finden, also habe ich hier einen erstellt:
https://code.google.com/p/android/issues/detail?id=37380

Bearbeiten: Meine Arbeit dafür (danke für die Ideen @Gray) ist eigentlich ziemlich einfach. (Dies ist Trunkcated-Code, etwas Caching und dergleichen wird weggelassen)

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

Ihre Laufleistung hängt von der Nutzung ab, aber in manchen Fällen war dies etwa 15-20 Mal schneller, als wenn Sie es “richtig” machen würden.

  • Wow wirklich! Ich spreche speziell darüber, wie ekelhaft Method.equals() war in dem anderen Fehlerbericht.

    – Grau

    12. September 2012 um 20:39 Uhr

  • Ja, immer noch ein Thema. Da ich dachte, ich sei schlau, wollte ich einfach Method.invoke direkt in meiner Anmerkung aufrufen, da ich sah, dass der Code dort direkt in eine native Methode eintauchte. Nun, es stellt sich heraus, dass die native Methode schließlich AnnotationFactory.invoke() aufruft, was zu einem weiteren Method.equals()-Aufruf führt. Kann mir keine andere Arbeit um Geldautomaten vorstellen.

    – Benutzer931366

    13. September 2012 um 1:55 Uhr

  • In ORMLite habe ich Reflektion verwendet, um in die Android-Klassen einzudringen. Macht die Dinge 10x schneller, erfordert aber manuell abgestimmten Code: ormlite.svn.sourceforge.net/svnroot/ormlite/ormlite-android/…

    – Grau

    13. September 2012 um 1:59 Uhr


  • Danke für den Tipp Grey. Oben mit einer Lösung aktualisiert, die für mich hervorragend funktioniert. Ich habe nicht gesehen, dass Ihre Lösung “validateValue” aufruft, also schätze ich, dass Ihre Lösung nur dazu dient, das Vorhandensein von Anmerkungen zu überprüfen und tatsächliche Werte von Anmerkungsmitgliedern abzurufen?

    – Benutzer931366

    13. September 2012 um 18:07 Uhr

  • Meine Lösung ist spezifisch für meine Anmerkungen. Ich umgehe die Android-Anmerkungen vollständig. Ich habe keinen Leistungslauf gemacht, aber es sollte deutlich schneller sein.

    – Grau

    13. September 2012 um 18:34 Uhr

Ich denke, wenn Sie es schaffen, die RUNTIME-Aufbewahrungsrichtlinie zu ändern, sollte es nicht so langsam sein.

EDIT: Ich weiß, für Ihr Projekt ist das möglicherweise keine Option. Vielleicht liegt es eher an dem, was Sie mit dieser Anmerkung machen, als an der schlechten Leistung im Allgemeinen.

1094190cookie-checkWarum sind Anmerkungen unter Android ein solches Leistungsproblem (langsam)?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy