Java 为什么Android下的注释会出现这样的性能问题(速度慢)?
我是这本书的主要作者,它在类上使用Java注释来构建数据库模式。对于我们的软件包来说,一个重大的启动性能问题是在Android1.6下调用注释方法。我在3.0中看到了相同的行为 我们看到下面的简单注释代码是难以置信的GC密集型代码,并且是一个真正的性能问题。在快速的Android设备上,1000次调用注释方法几乎需要一秒钟的时间。在我的Macbook Pro上运行的相同代码可以在同一时间完成2800万次(原文如此)调用。我们有一个注释,其中有25个方法,我们希望每秒完成50多个方法 有人知道为什么会发生这种情况吗?是否有任何解决办法?在缓存这些信息方面,ORMLite当然可以做一些事情,但是我们可以在Android下“修复”注释吗?谢谢Java 为什么Android下的注释会出现这样的性能问题(速度慢)?,java,android,performance,garbage-collection,annotations,Java,Android,Performance,Garbage Collection,Annotations,我是这本书的主要作者,它在类上使用Java注释来构建数据库模式。对于我们的软件包来说,一个重大的启动性能问题是在Android1.6下调用注释方法。我在3.0中看到了相同的行为 我们看到下面的简单注释代码是难以置信的GC密集型代码,并且是一个真正的性能问题。在快速的Android设备上,1000次调用注释方法几乎需要一秒钟的时间。在我的Macbook Pro上运行的相同代码可以在同一时间完成2800万次(原文如此)调用。我们有一个注释,其中有25个方法,我们希望每秒完成50多个方法 有人知道为什
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;
}
编辑:
@candrews为我指出了正确的方向后,我对代码做了一些修改。性能问题似乎是由Method.equals()
中一些糟糕的、粗俗的代码造成的。它调用这两种方法的toString()
,然后比较它们。每个toString()
都使用StringBuilder
和一系列没有良好初始化大小的append方法。通过比较字段来执行.equals
,速度会明显加快
编辑:
我得到了一个有趣的改进。我们现在使用反射查看
AnnotationFactory
类内部,以直接读取字段列表。这使反射类的速度提高了20倍,因为它绕过了使用方法.equals()
调用的调用。这不是一个通用的解决方案,但下面是来自的Java代码。有关通用解决方案,请参阅。我认为,如果您设法更改运行时保留策略,它应该不会那么慢
编辑:我知道,对于你的项目,这可能不是一个选项。也许这更多的是您如何使用该注释的问题,而不是总体性能差的问题 谷歌已经承认了这个问题,并在“后蜂巢”中修复了它
因此,至少他们知道这一点,并且已经为将来的某个版本修复了它。接下来,在对注释调用方法时仍然存在一个问题。candrews在上面列出的bug修复了getAnnotation()的缓慢性,但是由于方法.equals()的问题,对注释调用方法仍然是一个问题 找不到方法.equals()的错误报告,因此我在此处创建了一个错误报告: 编辑: 因此,我的这项工作(感谢@Gray的创意)实际上非常简单。 (这是代码,省略了一些缓存等)
您的里程数会根据使用情况而有所不同,但在5月份,这比“正确的方式”快了15-20倍。这是一个通用版本的Gray和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;
}
}
以下是我如何将它集成到:。如果对
foo
属性使用int
而不是String
,您会看到类似的计时吗?可能是字符串池的问题?与int
或任何其他类型相同。这是关于注释的,而不是它们所注释的内容。Tnx.@Gray根据该问题以及该答案评论中引用的问题,这一问题已得到解决。你知道哪个版本的安卓系统可以安全地删除或mlite配置文件
?是的运行时
需要保留。查找注释方法肯定是个问题,而不是我在用它做什么。谢谢@candrews。我不能百分之百确定你所列举的问题是否是你的过错,但肯定是你的过错。看起来Method.equals()
才是真正的罪魁祸首。我已经对这个错误发表了评论。刚刚得到确认,这个问题确实是错误的。再次感谢@candrews。我认为仍然存在重大问题。我刚归档哇,真的!我特别提到了grossMethod.equals()
在另一个bug报告中的作用。是的,仍然是一个问题。所以,我认为我很聪明,我只是想直接在我的注释上调用Method.invoke,因为我看到那里的代码正好嵌入了一个本机方法。事实证明,本机方法最终调用AnnotationFactory.invoke(),这将导致另一个方法.equals()调用。在ORMLite中,我使用反射深入研究了Android类。使事情快10倍,但它需要手动调整代码:谢谢提示灰色。上面更新了一个对我来说非常有效的解决方案。我没有看到您的解决方案调用“validateValue”,因此我猜您的解决方案仅用于检查批注的存在并从批注成员检索实际值?我的解决方案特定于我的批注。我完全绕过了Android注释。我还没有做过性能测试,但应该会快得多。+1良好的通用解决方案。对于代码中的解决方案,您没有给我(或ORMLite)信任,我感到有点惊讶。我会的,这节省了我无数的时间。每个人都在这条线上摇摆!
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);
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;
}
}
Device Default Hack
HTC Desire 2.3.7 11094 730
Emulator 4.0.4 3157 528
Galaxy Nexus 4.3 1248 392