43

私は何千回も呼び出すコードField.setを書いています。Field.get明らかに、これはリフレクションのために非常に遅いです。

Java 7を使用してパフォーマンスを改善できるかどうかを確認したいと思いMethodHandleます。これまでのところ、私が持っているものは次のとおりです。

の代わりにfield.set(pojo, value)、私はやっています:

private static final Map<Field, MethodHandle> setHandles = new HashMap<>();

MethodHandle mh = setHandles.get(field);
if (mh == null) {
    mh = lookup.unreflectSetter(field);
    setHandles.put(field, mh);
}
mh.invoke(pojo, value);

ただし、これは、リフレクションを使用した Field.set 呼び出しよりもパフォーマンスが優れているようには見えません。ここで何か間違ったことをしていますか?

invokeExactを使用すると高速になる可能性があることを読みましたが、それを使用しようとするとjava.lang.invoke.WrongMethodTypeException.

Field.set または Field.get への繰り返し呼び出しを最適化できた人はいますか?

4

4 に答える 4

72

2015-06-01: ハンドルが静的である場合の別のケースに関する @JoeC のコメントを反映するように更新されました。また、最新の JMH に更新し、最新のハードウェアで再実行しました。結論はほぼ変わらない。

適切なベンチマークを行ってください。おそらくJMHではそれほど難しいことではありません。それができれば、答えは自ずと見えてきます。また、適切な使用法を示すこともできますinvokeExact(コンパイルして実行するには、ターゲット/ソース 1.7 が必要です):

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MHOpto {

    private int value = 42;

    private static final Field static_reflective;
    private static final MethodHandle static_unreflect;
    private static final MethodHandle static_mh;

    private static Field reflective;
    private static MethodHandle unreflect;
    private static MethodHandle mh;

    // We would normally use @Setup, but we need to initialize "static final" fields here...
    static {
        try {
            reflective = MHOpto.class.getDeclaredField("value");
            unreflect = MethodHandles.lookup().unreflectGetter(reflective);
            mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
            static_reflective = reflective;
            static_unreflect = unreflect;
            static_mh = mh;
        } catch (IllegalAccessException | NoSuchFieldException e) {
            throw new IllegalStateException(e);
        }
    }

    @Benchmark
    public int plain() {
        return value;
    }

    @Benchmark
    public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
        return (int) reflective.get(this);
    }

    @Benchmark
    public int dynamic_unreflect_invoke() throws Throwable {
        return (int) unreflect.invoke(this);
    }

    @Benchmark
    public int dynamic_unreflect_invokeExact() throws Throwable {
        return (int) unreflect.invokeExact(this);
    }

    @Benchmark
    public int dynamic_mh_invoke() throws Throwable {
        return (int) mh.invoke(this);
    }

    @Benchmark
    public int dynamic_mh_invokeExact() throws Throwable {
        return (int) mh.invokeExact(this);
    }

    @Benchmark
    public int static_reflect() throws InvocationTargetException, IllegalAccessException {
        return (int) static_reflective.get(this);
    }

    @Benchmark
    public int static_unreflect_invoke() throws Throwable {
        return (int) static_unreflect.invoke(this);
    }

    @Benchmark
    public int static_unreflect_invokeExact() throws Throwable {
        return (int) static_unreflect.invokeExact(this);
    }

    @Benchmark
    public int static_mh_invoke() throws Throwable {
        return (int) static_mh.invoke(this);
    }

    @Benchmark
    public int static_mh_invokeExact() throws Throwable {
        return (int) static_mh.invokeExact(this);
    }

}

1x4x2 i7-4790K、JDK 8u40、Linux x86_64 では、次の結果が得られます。

Benchmark                             Mode  Cnt  Score   Error  Units
MHOpto.dynamic_mh_invoke              avgt   25  4.393 ± 0.003  ns/op
MHOpto.dynamic_mh_invokeExact         avgt   25  4.394 ± 0.007  ns/op
MHOpto.dynamic_reflect                avgt   25  5.230 ± 0.020  ns/op
MHOpto.dynamic_unreflect_invoke       avgt   25  4.404 ± 0.023  ns/op
MHOpto.dynamic_unreflect_invokeExact  avgt   25  4.397 ± 0.014  ns/op
MHOpto.plain                          avgt   25  1.858 ± 0.002  ns/op
MHOpto.static_mh_invoke               avgt   25  1.862 ± 0.015  ns/op
MHOpto.static_mh_invokeExact          avgt   25  1.859 ± 0.002  ns/op
MHOpto.static_reflect                 avgt   25  4.274 ± 0.011  ns/op
MHOpto.static_unreflect_invoke        avgt   25  1.859 ± 0.002  ns/op
MHOpto.static_unreflect_invokeExact   avgt   25  1.858 ± 0.002  ns/op

...これは、この特定のケースでは MH がリフレクションよりもはるかに高速であることを示唆しています (これは、プライベート フィールドに対するアクセス チェックが呼び出し時ではなくルックアップ時に行われるためです)。dynamic_*ケースは、MethodHandlesand/orFieldsが静的に認識されていない場合 (たとえば、プルされた場合など) をシミュレートしMap<String, MethodHandle>ます。逆にstatic_*、呼び出し元が静的にわかっているケースです。

場合によっては、リフレクションのパフォーマンスが MethodHandles と同等でdynamic_*あることに注意してください。これは、リフレクションが JDK 8 でさらに大幅に最適化されているためです (実際には、独自のフィールドを読み取るためにアクセス チェックが必要ないため)。したがって、答えは「ちょうど」である可能性があります。 JDK 8に切り替えます;)

static_*MethoHandles.invoke呼び出しが積極的にインライン化されるため、ケースはさらに高速です。これにより、MH の場合の型チェックの一部が省略されます。ただし、リフレクションの場合は、まだクイック チェックが存在するため、遅れをとっています。

于 2014-03-11T22:09:27.497 に答える
6

ホルガーのおかげで編集私は本当にinvokeExactを使用する必要があることに気づいたので、他のjdkに関するものを削除してinvokeExactのみを使用することにしました... -serverを使用するかどうかはまだ違いはありませんが

リフレクションを使用する場合と MethodHandles を使用する場合の主な違いは、リフレクションの場合、すべての呼び出しに対してセキュリティ チェックが行われることです。MethodHandles の場合は、ハンドルの作成に対してのみです。

これを見れば

class Test {
    public Object someField;
    public static void main(String[] args) throws Exception {
        Test t = new Test();
        Field field = Test.class.getDeclaredField("someField");
        Object value = new Object();
        for (int outer=0; outer<50; outer++) {
            long start = System.nanoTime();
            for (int i=0; i<100000000; i++) {
                field.set(t, value);
            }
            long time = (System.nanoTime()-start)/1000000;
            System.out.println("it took "+time+"ms");
        }
    }
}

次に、jdk7u40 で 45000 ミリ秒のコンピューター時間を取得します (ただし、jdk8 と 7u25 より前のパフォーマンスははるかに優れています)。

次に、ハンドルを使用した同じプログラムを見てみましょう

class Test {
    public Object someField;
    public static void main(String[] args) throws Throwable {
        Test t = new Test();
        Field field = Test.class.getDeclaredField("someField");
        MethodHandle mh = MethodHandles.lookup().unreflectSetter(field);
        Object value = new Object();
        for (int outer=0; outer<50; outer++) {
            long start = System.nanoTime();
            for (int i=0; i<100000000; i++) {
                mh.invokeExact(t, value);
            }
            long time = (System.nanoTime()-start)/1000000;
            System.out.println("it took "+time+"ms");
        }
    }
}

7u40 は、およそ 1288ms と言います。というわけで、7u40でホルガーの30回を確認できました。7u06 では、リフレクションが数倍速くなり、jdk8 ではすべてが新しくなったため、このコード ハンドルは遅くなります。

改善が見られなかった理由については... 言うのは難しい. 私が行ったのはマイクロベンチマークでした。これでは、実際のアプリケーションについては何もわかりません。しかし、これらの結果を使用すると、古いバージョンの jdk を使用しているか、ハンドルを十分に再利用していないと推測できます。ハンドルを実行すると高速になる可能性がありますが、ハンドルの作成にはフィールドの作成よりもはるかに多くのコストがかかる可能性があるためです。

ここで最大の問題点は... Google appengine にこれが必要であることがわかりました...そして、必要なだけローカルでテストできることを言わなければなりません。最終的に重要なのは、Google でのアプリケーションのパフォーマンスです。サイトになります。Afaik 彼らは変更された OpenJDK を使用していますが、どのバージョンにどの変更を加えたかについては言及していません。Jdk7が非常に不安定であるため、運が悪いかどうかはわかりません。おそらく、彼らはリフレクションのために特別なコードを追加したのでしょう。そして、それを無視しても... 支払いモデルが再び変更された可能性がありますが、通常はコストがかかるため、キャッシュによってデータストアへのアクセスを避けたいと考えています。それでも問題が解決しない場合、ハンドルが平均で 10.000 回呼び出されることは現実的ですか?

于 2014-03-11T10:18:57.810 に答える
6

JDK 7 および 8 の MethodHandles にはキャッチ 22があります(JDK 9 以降はまだテストしていません) 。静的フィールドにある場合、MethodHandle は高速です (直接アクセスと同じくらい高速です)。そうでなければ、反射と同じくらい遅くなります。フレームワークが n 個の getter または setter を反映している場合 (n はコンパイル時に不明)、MethodHandles はおそらく役に立たないでしょう。

リフレクションを高速化するためのさまざまなアプローチすべてをベンチマークした記事を書きました。

LambdaMetafactory (またはコード生成などのよりエキゾチックなアプローチ) を使用して、ゲッターとセッターの呼び出しを高速化します。ゲッターの要点は次のとおりです(セッターの場合は a を使用しますBiConsumer):

public final class MyAccessor {

    private final Function getterFunction;

    public MyAccessor() {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        CallSite site = LambdaMetafactory.metafactory(lookup,
                "apply",
                MethodType.methodType(Function.class),
                MethodType.methodType(Object.class, Object.class),
                lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class)),
                MethodType.methodType(String.class, Person.class));
        getterFunction = (Function) site.getTarget().invokeExact();
    }

    public Object executeGetter(Object bean) {
        return getterFunction.apply(bean);
    }

}
于 2018-01-24T10:08:43.763 に答える