私は常に回避してきましたJavaスローの評判に基づいて、リフレクションソレイ。現在のプロジェクトの設計で、それを使用できるとコードがより読みやすくエレガントになるという点に達しました、それで私はそれをやってみることにしました。
違いに驚いただけで、実行時間がほぼ100倍長いことに気づきました。空のクラスをインスタンス化するだけのこの単純な例でも、信じられないほどです。
class B {
}
public class Test {
public static long timeDiff(long old) {
return System.currentTimeMillis() - old;
}
public static void main(String args[]) throws Exception {
long numTrials = (long) Math.pow(10, 7);
long millis;
millis = System.currentTimeMillis();
for (int i=0; i<numTrials; i++) {
new B();
}
System.out.println("Normal instaniation took: "
+ timeDiff(millis) + "ms");
millis = System.currentTimeMillis();
Class<B> c = B.class;
for (int i=0; i<numTrials; i++) {
c.newInstance();
}
System.out.println("Reflecting instantiation took:"
+ timeDiff(millis) + "ms");
}
}
だから本当に、私の質問は
なぜこれが遅いのですか?私が間違っていることはありますか? (上の例でも違いを示しています)。通常のインスタンス化よりも実際に100倍遅くなるとは信じられません。
コードをデータとして扱うために使用できる他の方法はありますか(今のところJava)で立ち往生していることに注意してください)
テストに欠陥がある可能性があります。 通常、JVMは通常のインスタンス化を最適化する可能性がありますが、リフレクトユースケースの最適化を行うことができませんでした 。
時間とは何かを不思議に思っている人のために、ウォームアップフェーズを追加し、作成したオブジェクトを維持するために配列を使用しました(実際のプログラムの動作に似ています)。私は自分のOSX、jdk7システムでテストコードを実行し、これを取得しました。
インスタンス化の反映にかかった時間:5180ms
通常のインスタンス化にかかった時間:2001ミリ秒
変更されたテスト:
public class Test {
static class B {
}
public static long timeDiff(long old) {
return System.nanoTime() - old;
}
public static void main(String args[]) throws Exception {
int numTrials = 10000000;
B[] bees = new B[numTrials];
Class<B> c = B.class;
for (int i = 0; i < numTrials; i++) {
bees[i] = c.newInstance();
}
for (int i = 0; i < numTrials; i++) {
bees[i] = new B();
}
long nanos;
nanos = System.nanoTime();
for (int i = 0; i < numTrials; i++) {
bees[i] = c.newInstance();
}
System.out.println("Reflecting instantiation took:" + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");
nanos = System.nanoTime();
for (int i = 0; i < numTrials; i++) {
bees[i] = new B();
}
System.out.println("Normal instaniation took: " + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");
}
}
いくつかの明らかな理由で反射が遅い:
JIT
にも当てはまりますExceptions
_InvocationTargetException
sへのラップ、再スローなどによってドレスアップする必要があります。何かが100倍遅いからといって、リフレクションがプログラムを設計するための「正しい方法」であると想定して、それがあなたにとって遅すぎるとは限りません。たとえば、IDEはリフレクションを多用していると思いますが、パフォーマンスの観点から見ると、IDEはほとんど問題ありません。
結局のところ、反射のオーバーヘッドは、無意味に変換される可能性が高いと比較して、たとえば、XMLを解析するか、データベースにアクセスする!
覚えておくべきもう1つのポイントは、マイクロベンチマークは、何かが実際にどれほど高速であるかを判断するための悪名高い欠陥メカニズムであるということです。 Tim Bender'sremarks と同様に、JVMは「ウォームアップ」に時間がかかり、JITはオンザフライなどでコードのホットスポットを再最適化できます。 。
Bをインスタンス化するためのJITされたコードは、信じられないほど軽量です。基本的には、十分なメモリを割り当てる必要があり(GCが必要でない限り、ポインタをインクリメントするだけです)、それだけです。実際に呼び出すコンストラクタコードはありません。 JITがそれをスキップするかどうかはわかりませんが、どちらにしても、それほど多くのことはありません。
これを、リフレクションが行うすべてのことと比較してください。
...そしておそらく私が考えもしなかった他の事柄。
通常、リフレクションはパフォーマンスが重要なコンテキストでは使用されません。そのような動的な動作が必要な場合は、代わりに [〜#〜] bcel [〜#〜] のようなものを使用できます。
コンストラクタをアクセス可能にすると、実行速度が大幅に向上するようです。現在、他のバージョンよりも10〜20倍遅いだけです。
Constructor<B> c = B.class.getDeclaredConstructor();
c.setAccessible(true);
for (int i = 0; i < numTrials; i++) {
c.newInstance();
}
Normal instaniation took: 47ms
Reflecting instantiation took:718ms
また、サーバーVMを使用すると、さらに最適化できるため、速度が3〜4倍遅くなります。これは非常に典型的なパフォーマンスです。 記事 Geoがリンクしていることは良い読み物です。
Normal instaniation took: 47ms
Reflecting instantiation took:140ms
ただし、-XX:+ DoEscapeAnalysisを使用してスカラー置換を有効にすると、JVMは通常のインスタンス化を最適化できます(0-15msになります)が、反射インスタンス化は同じままです。
@Tim Benderのコードは、私のマシン(jdk_1.8_45、os_x 10.10、i7、16G)でこれらの結果を提供します:
Reflecting instantiation took:1139ms Normal instaniation took: 4969ms
最新のJVMのようですが、リフレクションコードも最適化されます。