web-dev-qa-db-ja.com

なぜ注釈がAndroid=のようなパフォーマンスの問題(遅い)の下にあるのですか?)

私は ORMLite の筆頭著者です。これは、クラスにJavaアノテーションを使用してデータベーススキーマを構築します。パッケージの起動時の大きなパフォーマンス問題は、 Android 1.6の下のアノテーションメソッド。3.0まで同じ動作が見られます。

次の単純な注釈コードは信じられないほどGCに負荷がかかり、実際のパフォーマンスの問題であることがわかります。アノテーションメソッドへの1000回の呼び出しは、高速のAndroid=デバイスで1秒近くかかります。私のMacbook Proで実行されている同じコードは、同時に2800万回の(sic)呼び出しを実行できます。アノテーションがあります。これには25のメソッドがあり、1秒あたり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クラス内を調べ、フィールドのリストを直接読み取っています。これにより、method.equals()呼び出しを使用している呼び出しがバイパスされるため、リフレクションクラスが20times速くなります。これは一般的なソリューションではありませんが、Javaコード ORMLite SVNリポジトリ からのコードです。一般的なソリューションについては、以下の yanchenkoの回答 を参照してください。

Googleはこの問題を認識し、「ポストハニカム」で修正しました

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

したがって、少なくとも彼らはそれについて知っており、将来のいくつかのバージョンで修正すると思われます。

22
candrews

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

}

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

6
yanchenko

これをフォローアップするために、アノテーションでメソッドを呼び出すときはまだ問題があります。 candrewsによって上記にリストされたバグはgetAnnotation()の速度低下を修正しますが、アノテーションでメソッドを呼び出すことは、Method.equals()の問題により依然として問題です。

Method.equals()のバグレポートが見つからなかったため、ここに作成しました: https://code.google.com/p/Android/issues/detail?id=3738

編集:したがって、これに対する私の回避策(@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倍速くなる場合があります。

5
user931366

実行時保存ポリシーを変更できたとしても、それほど遅くないはずです。

編集:私は知っています、あなたのプロジェクトのためにそれはオプションではないかもしれません。おそらくそれは、一般的にパフォーマンスが悪いというよりは、そのアノテーションを使って何をしているのかという問題です。

1
Mister Smith