为什么Android下的注释会出现性能问题(速度慢)?

Gra*_*ray 50 java performance android garbage-collection annotations

我是ORMLite的主要作者,它在类上使用Java注释来构建数据库模式.我们的软件包的一个重大启动性能问题原来是在Android 1.6下调用注释方法.我看到3.0的相同行为.

我们看到以下简单的注释代码是令人难以置信的 GC密集型和真正的性能问题.在快速的Android设备上,1000次调用注释方法需要几乎一秒钟.在我的Macbook Pro上运行的相同代码可以同时执行2800万(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;
}
Run Code Online (Sandbox Code Playgroud)

这导致以下日志输出:

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
Run Code Online (Sandbox Code Playgroud)

编辑:

在@candrews指出我正确的方向后,我做了一些探索代码.性能问题似乎是由一些可怕的粗略代码引起的Method.equals().它调用toString()两种方法,然后比较它们.每个toString()使用StringBuilder一堆附加方法没有良好的初始化大小.在做.equals比较的字段将显著加快.

编辑:

我得到了一个有趣的反射性能提升.我们现在使用反射来查看AnnotationFactory类内部以直接读取字段列表.这使得反射类对我们来说快了20 ,因为它绕过了使用method.equals()调用的调用.它不是通用的解决方案,而是来自ORMLite SVN存储库的Java代码.有关通用解决方案,请参阅下面的yanchenko的答案.

can*_*ews 22

谷歌承认了这个问题,并将其修为"post-Honeycomb"

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

所以至少他们知道它并且据说为未来版本修复了它.

  • 我认为仍存在重大问题.我刚刚提交了http://code.google.com/p/android/issues/detail?id=43827 (4认同)

yan*_*nko 6

这是Grayuser931366的一个通用版本的想法:

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

}
Run Code Online (Sandbox Code Playgroud)

我用4个元素对注释进行了基准测试.
10000次迭代的毫秒次,要么获得所有值的值,要么调用上面的方法:

     Device        Default  Hack
HTC Desire 2.3.7    11094   730
Emulator 4.0.4      3157    528
Galaxy Nexus 4.3    1248    392
Run Code Online (Sandbox Code Playgroud)

以下是我将其集成到DroidParts中的方法:https://github.com/yanchenko/droidparts/commit/93fd1a1d6c76c2f4abf185f92c5c59e285f8bc69.

  • +1好的通用解决方案.我有点惊讶你没有给我(或ORMLite)信用代码中的解决方案.我会. (2认同)

use*_*366 5

为了跟进这一点,在注释上调用方法时仍然存在问题.上面列出的candrews错误修复了getAnnotation()的缓慢,但由于Method.equals()问题,调用注释上的方法仍然是一个问题.

找不到Method.equals()的错误报告,所以我在这里创建了一个:https://code.google.com/p/android/issues/detail? id = 37380

编辑:所以我的工作(感谢@Gray的想法),实际上非常简单.(这是trunkcated代码,一些缓存等省略)

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);
Run Code Online (Sandbox Code Playgroud)

您的里程数会根据使用情况而有所不同,但在可能的情况下,这比"正确的方式"快15-20倍