Ответ 1
Google подтвердила эту проблему и исправила ее "post-Honeycomb"
Так что, по крайней мере, они знают об этом и предположительно исправили его для какой-то будущей версии.
Я ведущий автор ORMLite, который использует аннотации Java для классов для построения схем баз данных. Большая проблема производительности стартового пакета для нашего пакета оказывается вызовом методов аннотации в Android 1.6. Я вижу то же поведение до 3,0.
Мы видим, что следующий простой код аннотации невероятно насыщен GC и реальная проблема с производительностью. 1000 вызовов метода аннотации занимают почти секунду на быстром Android-устройстве. Тот же код, который работает на моем Macbook Pro, может выполнять 28 миллионов (sic) звонков в одно и то же время. У нас есть аннотация, в которой есть 25 методов, и мы хотели бы сделать более 50 из них в секунду.
Кто-нибудь знает, почему это происходит, и если есть какая-то работа? Есть вещи, которые ORMLite может сделать с точки зрения кеширования этой информации, но есть ли что-нибудь, что мы можем сделать, чтобы "исправить" аннотации под Android? Спасибо.
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;
}
В результате получается следующий выход журнала:
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:
После того, как @candrews указал мне в правильном направлении, я немного проговорил над кодом. Проблема производительности связана с каким-то ужасным, грубым кодом в Method.equals()
. Он вызывает toString()
обоих методов, а затем сравнивает их. Каждый toString()
использует StringBuilder
с кучей методов добавления без хорошего размера инициализации. Выполнение .equals
путем сравнения полей будет значительно быстрее.
EDIT:
Мне понравилось интересное улучшение производительности. Мы теперь используем рефлексию, чтобы заглянуть внутрь класса AnnotationFactory
, чтобы прочитать список полей напрямую. Это делает класс отражения в 20 раз быстрее для нас, поскольку он обходит вызов, который использует вызов Method.equals()
. Это не общее решение, а вот код Java из ORMLite SVN репозиторий. Для общего решения см. ответ Янченко ниже.
Google подтвердила эту проблему и исправила ее "post-Honeycomb"
Так что, по крайней мере, они знают об этом и предположительно исправили его для какой-то будущей версии.
Вот общая версия идеи Серый и user931366:
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;
}
}
Я сравнивал аннотацию с 4 элементами.
Миллисекундное время для 10000 итераций либо получения значений всех из них, либо вызова метода выше:
Device Default Hack
HTC Desire 2.3.7 11094 730
Emulator 4.0.4 3157 528
Galaxy Nexus 4.3 1248 392
Вот как я интегрировал его в DroidParts: https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69.
Чтобы следить за этим, проблема возникает при вызове методов аннотаций. Ошибка, указанная выше с помощью значков, исправляет задержку getAnnotation(), но вызов метода в аннотации по-прежнему является проблемой из-за проблем Method.equals().
Не удалось найти отчет об ошибке для Method.equals(), поэтому я создал его здесь: https://code.google.com/p/android/issues/detail?id=37380
Изменить: Поэтому моя работа для этого (спасибо за идеи @Gray), на самом деле довольно проста. (это трансконтированный код, некоторое кэширование и т.д.)
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);
Ваш пробег будет варьироваться в зависимости от использования, но в случае, если это было примерно в 15-20 раз быстрее, чем выполнение "правильного пути"
Я думаю, что если вам удастся изменить политику хранения RUNTIME, это не должно быть так медленно.
EDIT: Я знаю, для вашего проекта это может быть не вариант. Возможно, это больше проблема того, что вы делаете с этой аннотацией, а не с плохими результатами в целом.