Подтвердить что ты не робот

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

Я ведущий автор 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 репозиторий. Для общего решения см. ответ Янченко ниже.

4b9b3361

Ответ 1

Google подтвердила эту проблему и исправила ее "post-Honeycomb"

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

Я думаю, что если вам удастся изменить политику хранения RUNTIME, это не должно быть так медленно.

EDIT: Я знаю, для вашего проекта это может быть не вариант. Возможно, это больше проблема того, что вы делаете с этой аннотацией, а не с плохими результатами в целом.