【问题标题】:Why are annotations under Android such a performance issue (slow)?为什么Android下的注解会出现这样的性能问题(慢)?
【发布时间】:2011-11-17 01:28:00
【问题描述】:

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

我们看到以下简单的注释代码是难以置信 GC 密集型和真正的性能问题。在快速的 Android 设备上,对注释方法的 1000 次调用几乎需要一秒钟。在我的 Macbook Pro 上运行的相同代码可以同时进行 2800 万次(原文如此)调用。我们有一个注释,其中包含 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() 调用的调用。这不是一个通用的解决方案,但这里是来自ORMLite SVN repository 的 Java 代码。有关通用解决方案,请参阅yanchenko's answer below

【问题讨论】:

  • 如果您对foo 属性使用int 而不是String,您会看到类似的时间安排吗?可能是字符串池的问题?
  • int 或任何其他类型同时使用。这是关于注释而不是它们注释的内容。 Tnx。
  • @Gray 根据candrews referenced 的问题以及该答案的cmets 中引用的问题,此问题已得到修复。您知道删除ORMLite config file 的Android 版本是安全的吗?

标签: java android performance garbage-collection annotations


【解决方案1】:

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

}

我用 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

【讨论】:

  • +1 很好的通用解决方案。我有点惊讶你没有给我(或 ORMLite)代码中的解决方案的功劳。我会的。
  • 这为我节省了无数时间。这个线程上的每个人都摇滚!
【解决方案2】:

为了跟进这个问题,在注解上调用方法时这里仍然存在问题。 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 倍

【讨论】:

  • 哇,真的!我在另一个错误报告中特别谈到了Method.equals() 的严重程度。
  • 是的,仍然是个问题。所以,认为我很聪明,我只是想直接在我的注释上调用 Method.invoke,因为我看到那里的代码直接进入了一个本地方法。事实证明,本机方法最终调用 AnnotationFactory.invoke() 导致另一个 Method.equals() 调用。想不出任何其他解决 ATM 的方法。
  • 在 ORMLite 中,我使用反射深入 Android 类。速度提高 10 倍,但需要手动调整代码:ormlite.svn.sourceforge.net/svnroot/ormlite/ormlite-android/…
  • 感谢格雷的提示。上面更新了一个对我有用的解决方案。我没有看到您的解决方案调用“validateValue”,所以我猜您的解决方案仅用于检查注释是否存在并从注释成员中检索实际值?
  • 我的解决方案特定于我的注释。我完全绕过了 Android 注释。我还没有进行性能测试,但它应该会更快。
【解决方案3】:

Google 已经承认了这个问题并在“蜂窝后”修复了它

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

所以至少他们知道它并且据说已经为将来的某个版本修复了它。

【讨论】:

  • 感谢@candrews。我不能 100% 确定你列出的问题是有问题的,但肯定很接近。看起来Method.equals() 是真正的罪魁祸首。我已经评论了这个错误。
  • 刚刚确认问题确实存在问题。再次感谢@candrews。
  • 我觉得还是有大问题。我刚刚提交了code.google.com/p/android/issues/detail?id=43827
【解决方案4】:

我认为如果您设法更改 RUNTIME 保留政策,它应该不会那么慢。

编辑:我知道,对于您的项目,这可能不是一个选择。也许这更多的是您使用该注释所做的事情,而不是一般的不良性能。

【讨论】:

  • 是的,RUNTIME 保留是必需的。这绝对是查找注释方法的问题,而不是我正在用它做什么。
猜你喜欢
  • 2017-03-03
  • 2021-09-17
  • 1970-01-01
  • 2022-11-01
  • 1970-01-01
  • 2022-01-24
  • 2020-05-15
  • 2012-07-17
  • 2014-12-15
相关资源
最近更新 更多