Warum sind Anmerkungen unter Android ein solches Leistungsproblem (langsam)?
Lesezeit: 6 Minuten
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;
}
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
Kerzen
Google hat das Problem erkannt und „post-Honeycomb“ behoben
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.
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:
+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
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.
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.
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.
10941900cookie-checkWarum sind Anmerkungen unter Android ein solches Leistungsproblem (langsam)?yes
Sehen Sie ähnliche Timings, wenn Sie eine verwenden
int
eher als einString
für deinfoo
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