¿Por qué son las anotaciones bajo Android un problema de rendimiento (lento)?

Soy el autor principal de ORMLite que utiliza anotaciones de Java en clases para crear esquemas de base de datos. Un gran problema de rendimiento de inicio para nuestro paquete resulta ser la llamada de métodos de anotación bajo Android 1.6. Veo el mismo comportamiento a través de 3.0.

Estamos viendo que el siguiente código de anotación simple es increíblemente GC intensivo y un problema de rendimiento real. 1000 llamadas a un método de anotación tardan casi un segundo en un dispositivo Android rápido. El mismo código que se ejecuta en mi Macbook Pro puede hacer 28 millones (sic) de llamadas en el mismo tiempo. Tenemos una anotación que tiene 25 métodos en él y nos gustaría hacer más de 50 de estos un segundo.

¿Alguien sabe por qué esto está sucediendo y si hay algún trabajo alrededor? Ciertamente hay cosas que ORMLite puede hacer en términos de almacenamiento en caché de esta información, pero ¿hay algo que podamos hacer para "corregir" las anotaciones en Android? Gracias.

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

Esto da como resultado la salida de registro siguiente:

 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 

EDITAR:

Después de @andrews me apuntó en la dirección correcta, hice un poco de empuje alrededor del código. El problema de rendimiento parece ser causado por algún terrible, código bruto en Method.equals() . Está llamando a toString() de ambos métodos y luego comparándolos. Cada toString() utiliza StringBuilder con un montón de métodos append sin un buen tamaño de inicialización. Hacer los .equals al comparar campos sería mucho más rápido.

EDITAR:

Se me dio una interesante mejora en el desempeño de la reflexión. Ahora estamos usando la reflexión para mirar dentro de la clase AnnotationFactory para leer la lista de campos directamente. Esto hace que la clase de reflexión 20 veces más rápido para nosotros, ya que ignora la invocación que está utilizando la llamada method.equals() . No es una solución genérica, pero aquí está el código Java del repositorio SVN de ORMLite . Para una solución genérica, vea la respuesta de yanchenko a continuación .

Google ha reconocido el problema y lo arregló "post-Honeycomb"

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

Así que al menos lo saben y lo han arreglado para una versión futura.

Esta es una versión genérica de la idea de Grey & 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; } } 

He comparado una anotación con 4 elementos.
Tiempos de milisegundos para 10000 iteraciones de obtener valores de todos ellos o llamar al método anterior:

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

He aquí cómo lo he integrado en DroidParts : https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69 .

Para hacer un seguimiento de esto, todavía hay un problema aquí cuando los métodos de llamada en las anotaciones. El error mencionado anteriormente por candrews corrige la lentitud de getAnnotation (), pero llamar a un método en la anotación sigue siendo un problema debido a los problemas Method.equals ().

No se pudo encontrar un informe de error para Method.equals (), por lo que creé uno aquí: https://code.google.com/p/android/issues/detail?id=37380

Edit: Así que mi trabajo alrededor de esto (gracias por las ideas @Gray), es realmente bastante simple. (Este es el código trunkcated, algunos de almacenamiento en caché y tal se omite)

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

Usted kilometraje puede variar en función del uso, pero en caso de que esto fue alrededor de 15-20 veces más rápido que hacerlo de la "manera correcta"

Creo que si logras cambiar la política de retención de RUNTIME, no debería ser tan lento.

EDIT: Lo sé, para su proyecto que no puede ser una opción. Tal vez es más un problema de lo que está haciendo con esa anotación en lugar de mal rendimiento en general.

  • ¿Cómo dibujar muchos rectángulos en lona con buen funcionamiento?
  • Cambiar el tamaño de las imágenes según la resolución de pantalla Android
  • Android Studio demasiado lento y laggy
  • Cómo utilizar Viewport Marker Management en Android
  • La notificación con setGroupSummary (true) no está visible en Android N
  • ¿Por qué WebView es mucho más rápido que un TextView?
  • Android - Declarativa vs interfaz de usuario programática
  • ¿Es Hibernate un exceso para una aplicación de Android?
  • Coracteres corrompidos o desaparecidos después de ejecutar Proguard
  • Despliegue de dibujo de pantalla de Android Canvas
  • ¿La codificación dura de la cadena afecta el rendimiento?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.