Почему аннотации под Android такие проблемы с производительностью (медленно)?

Я ведущий автор ORMLite, который использует аннотации Java для классов для построения схем баз данных. Большая проблема с производительностью стартапа для нашего пакета - это вызов методов аннотации в Android 1.6. Я вижу такое же поведение до 3.0.

Мы видим, что следующий простой код аннотации невероятно интенсивен для GC и представляет собой реальную проблему производительности. 1000 быстрых обращений к методу аннотации занимают почти секунду на быстром устройстве Android. Один и тот же код, работающий на моем Macbook Pro, может одновременно выполнять 28 миллионов вызовов. У нас есть аннотация, содержащая 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

РЕДАКТИРОВАТЬ:

После того, как @candrews указал мне правильное направление, я немного поковырялся в коде. Проблема производительности, похоже, вызвана каким-то ужасным, грубым кодом в Method.equals(), Это зовет toString() обоих методов, а затем сравнивая их. каждый toString() использование StringBuilder с кучей методов добавления без хорошего размера инициализации. Делать .equals при сравнении полей будет значительно быстрее.

РЕДАКТИРОВАТЬ:

Интересное улучшение производительности отражения было дано мне. Теперь мы используем отражение, чтобы заглянуть внутрь AnnotationFactory класс для чтения списка полей напрямую. Это делает класс отражения в 20 раз быстрее для нас, поскольку он обходит вызов, использующий method.equals() вызов. Это не общее решение, но вот код Java из репозитория ORMLite SVN. Для общего решения см . Ответ Янченко ниже.

4 ответа

Решение

Google признал проблему и исправил ее "после соты"

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

Так что, по крайней мере, они знают об этом и предположительно исправили это для какой-то будущей версии.

Вот общая версия идеи Грея& 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.

Чтобы решить эту проблему, здесь все еще есть проблема при вызове методов для аннотаций. Ошибка, указанная выше в candrews, исправляет медлительность 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, она не должна быть такой медленной.

РЕДАКТИРОВАТЬ: Я знаю, для вашего проекта это может быть не вариант. Возможно, это скорее проблема того, что вы делаете с этой аннотацией, чем плохая производительность в целом.

Другие вопросы по тегам