我是ORMLite的主要作者,它在类上使用Java来构建数据库模式。我们的包的一个很大的启动性能问题是在Android1.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()
都使用带有一堆append方法的StringBuilder
,但没有合适的初始化大小。通过比较字段来执行.equals
将会快得多。
编辑:
我得到了一个有趣的反射性能改进。我们现在使用反射来窥视AnnotationFactory
类内部,以直接读取字段列表。这使得反射类的速度提高了20倍,因为它绕过了使用method.equals()
调用的invoke。这不是一个通用的解决方案,但这里有来自ORMLite SVN repository的Java代码。有关通用解决方案,请参阅yanchenko's answer below。
发布于 2011-09-14 22:28:09
谷歌已经承认了这个问题,并将其修复为“后蜂巢”。
因此,至少他们知道它,并理应在未来的某个版本中修复它。
发布于 2013-08-06 03:39:30
以下是Gray's & user931366's想法的一个通用版本:
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。
发布于 2012-09-13 04:37:08
为了跟进这一点,在注释上调用方法时仍然存在一个问题。上面由candrews列出的bug修复了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);
您的里程数将根据使用情况而有所不同,但在5月份的情况下,这比使用“正确的方式”快15-20倍。
https://stackoverflow.com/questions/7417426
复制相似问题