回答:
そのとおり。リフレクションを介してクラスを検索すると、規模が大きくなるため、コストが高くなります。
リフレクションには動的に解決されるタイプが含まれるため、特定のJava仮想マシンの最適化を実行できません。その結果、リフレクティブ操作は非リフレクティブ操作よりもパフォーマンスが遅いため、パフォーマンスの影響を受けやすいアプリケーションで頻繁に呼び出されるコードのセクションでは回避する必要があります。
これは、Sun JRE 6u10を実行している私のマシンで5分でハッキングした簡単なテストです。
public class Main {
public static void main(String[] args) throws Exception
{
doRegular();
doReflection();
}
public static void doRegular() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = new A();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
public static void doReflection() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = (A) Class.forName("misc.A").newInstance();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
}
これらの結果:
35 // no reflection
465 // using reflection
ルックアップとインスタンス化は一緒に行われ、場合によってはルックアップをリファクタリングできますが、これは基本的な例にすぎません。
インスタンス化しただけでも、パフォーマンスに影響があります。
30 // no reflection
47 // reflection using one lookup, only instantiating
再び、YMMV。
はい、遅いです。
しかし、いまいましい#1ルールを忘れないでください-永続的な最適化はすべての悪の根源です
(まあ、DRYの場合は#1と結びつくかもしれません)
誰かが仕事で私に近づいてきて、これについて私に尋ねたら、私は次の数か月の間彼らのコードに非常に注意を払うだろうと私は誓います。
それが必要であると確信するまで、最適化してはいけません。それまでは、適切で読みやすいコードを記述してください。
ああ、私も愚かなコードを書くつもりはありません。可能な限り最もクリーンな方法を考えてください-コピーや貼り付けなどはしないでください(内部ループのようなものや、ニーズに最適なコレクションを使用することに注意してください-これらを無視することは、「最適化されていない」プログラミングではありません。 、それは「悪い」プログラミングです)
このような質問を聞くと、私はびっくりしますが、実際にすべてのルールを理解する前に、すべてのルールを自分で学習する必要があることを忘れています。誰かが「最適化された」何かをデバッグするのに1か月を費やした後に、それを得るでしょう。
編集:
このスレッドで興味深いことが起こりました。#1の回答を確認してください。これは、コンパイラが物事を最適化する上でどれほど強力であるかの例です。非反射インスタンス化は完全に除外できるため、テストは完全に無効です。
レッスン?クリーンできれいにコード化されたソリューションを作成し、それが遅すぎることが判明するまで、決して最適化しないでください。
A a = new A()がJVMによって最適化されていることがわかります。オブジェクトを配列に入れると、それらはあまりうまく機能しません。;)次のプリント...
new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns
public class Run {
private static final int RUNS = 3000000;
public static class A {
}
public static void main(String[] args) throws Exception {
doRegular();
doReflection();
doRegular();
doReflection();
}
public static void doRegular() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = new A();
}
System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
public static void doReflection() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = A.class.newInstance();
}
System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
}
これは、私のマシンでの差が約150 nsであることを示唆しています。
Class.getDeclaredMethod
呼び出す)、次にMethod.invoke
複数回呼び出す場合 リフレクションを1回使用しますか、それとも呼び出した回数だけ使用しますか?質問をフォローアップ、代わりにどのような場合にはMethod
、それであるConstructor
と私はConstructor.newInstance
複数回?
リフレクションよりも速い何かが本当に必要であり、それが単なる時期尚早の最適化ではない場合は、ASMまたはより高レベルのライブラリーを使用したバイトコード生成がオプションです。初めてバイトコードを生成することは、リフレクションを使用するよりも遅くなりますが、バイトコードが生成されると、通常のJavaコードと同じくらい高速で、JITコンパイラーによって最適化されます。
コード生成を使用するアプリケーションの例:
CGLIBがプロキシのバイトコードを生成するため、CGLIBによって生成されたプロキシでメソッドを呼び出すのは、Javaの動的プロキシよりもわずかに高速ですが、動的プロキシはリフレクションのみを使用します(CGLIBはメソッド呼び出しで約10倍速く測定されましたが、プロキシの作成は遅くなりました)。
JSerialは、リフレクションを使用する代わりに、シリアル化されたオブジェクトのフィールドを読み書きするためのバイトコードを生成します。JSerialのサイトにはいくつかのベンチマークがあります。
私は100%確信はありません(そして、私はソースを読む気がしません)が、Guiceは依存関係注入を行うためのバイトコードを生成すると思います。私が間違っていたら訂正してください。
「重要」は完全にコンテキストに依存しています。
リフレクションを使用して、構成ファイルに基づいて単一のハンドラーオブジェクトを作成し、残りの時間をデータベースクエリの実行に費やしている場合、それは重要ではありません。タイトなループでリフレクションを介して多数のオブジェクトを作成している場合、そうです、それは重要です。
一般に、設計の柔軟性(必要な場合!)は、パフォーマンスではなくリフレクションの使用を促進する必要があります。ただし、パフォーマンスが問題であるかどうかを判断するには、ディスカッションフォーラムから任意の応答を受け取るのではなく、プロファイリングする必要があります。
リフレクションには多少のオーバーヘッドがありますが、最近のVMでは以前よりもはるかに小さくなっています。
リフレクションを使用してプログラム内のすべての単純なオブジェクトを作成している場合、何かがおかしいです。時々それを使うことは、あなたが正当な理由があるとき、まったく問題になるべきではありません。
はい、Reflectionを使用するとパフォーマンスが低下しますが、最適化の可能な回避策はメソッドをキャッシュすることです。
Method md = null; // Call while looking up the method at each iteration.
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md = ri.getClass( ).getMethod("getValue", null);
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis");
// Call using a cache of the method.
md = ri.getClass( ).getMethod("getValue", null);
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");
結果は:
[java]ルックアップでメソッドを1000000回再帰的に呼び出すと5618ミリ秒かかりました
[java]キャッシュを使用してメソッドを1000000回再帰的に呼び出すと、270ミリ秒かかりました
オブジェクトの割り当ては、リフレクションの他の側面ほど絶望的ではありませんが、リフレクションは低速です。リフレクションベースのインスタンス化で同等のパフォーマンスを実現するには、インスタンス化されているクラスをjitが認識できるようにコードを記述する必要があります。クラスのIDを判別できない場合、割り当てコードをインライン化できません。さらに悪いことに、エスケープ分析が失敗し、オブジェクトをスタックに割り当てることができません。運が良ければ、JVMのランタイムプロファイリングがこのコードが熱くなったときに助けになり、どのクラスが優勢であるかを動的に判断し、そのクラスに最適化することができます。
このスレッドのマイクロベンチマークには深い欠陥があることに注意してください。そのため、一粒の塩を用意してください。ピーター・ローリーの欠点はこれまでで最も少ないものです。これは、ウォームアップの実行によってメソッドを不安定にし、エスケープ分析を(意識的に)無効にして、割り当てが実際に行われていることを確認します。ただし、それには問題があります。たとえば、膨大な数の配列ストアは、キャッシュとストアバッファーを無効にすることが予想されるため、割り当てが非常に速い場合、これは主にメモリベンチマークになります。(しかし、結論を正しく理解することについてピーターに称賛:違いは「2.5x」ではなく「150ns」です。彼はこの種のことを生計のために行っているのではないかと思います。)
興味深いことに、セキュリティチェックをスキップするsetAccessible(true)を設定すると、コストが20%削減されます。
setAccessible(true)なし
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
setAccessible(true)を使用
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
呼び出しを実行すると、これらの数値は線形にスケーリングしますか?
setAccessible()
は、特に複数の引数を持つメソッドの場合、一般にはるかに大きな違いがある可能性があるため、常に呼び出す必要があります。
はい、かなり遅くなります。そのためのコードを実行していましたが、現時点で使用できるメトリックスはありませんが、リフレクションを使用しないようにコードをリファクタリングする必要がありました。クラスがわかっている場合は、コンストラクタを直接呼び出します。
はい、JVMはコンパイル時にコードを最適化できないため、リフレクションによるオブジェクトの作成は常に遅くなります。詳細については、Sun / Java Reflectionチュートリアルを参照してください。
次の簡単なテストをご覧ください。
public class TestSpeed {
public static void main(String[] args) {
long startTime = System.nanoTime();
Object instance = new TestSpeed();
long endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
startTime = System.nanoTime();
try {
Object reflectionInstance = Class.forName("TestSpeed").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
}
}
Class.forName()
)をインスタンス化(newInstance())から分離する必要があることに注意してください。これらはパフォーマンス特性が大幅に異なり、適切に設計されたシステムでのルックアップの繰り返しを回避できる場合があるためです。
多くの場合、イントロスペクションを行うApacheコモンズのBeanUtilsまたはPropertyUtilsを使用できます(基本的に、クラスに関するメタデータをキャッシュするため、常にリフレクションを使用する必要はありません)。
それはターゲットメソッドがどれだけ軽い/重いかによります。ターゲットメソッドが非常に軽い場合(たとえば、getter / setter)、1〜3倍遅くなる可能性があります。ターゲットメソッドに約1ミリ秒以上かかる場合、パフォーマンスは非常に近くなります。ここに私がJava 8とreflectasmで行ったテストがあります:
public class ReflectionTest extends TestCase {
@Test
public void test_perf() {
Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult();
Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult();
Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult();
Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult();
Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult();
Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult();
Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult();
Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult();
}
public static class X {
public long m_01() {
return m_11();
}
public long m_02() {
return m_12();
}
public static long m_11() {
long sum = IntStream.range(0, 10).sum();
assertEquals(45, sum);
return sum;
}
public static long m_12() {
long sum = IntStream.range(0, 10000).sum();
assertEquals(49995000, sum);
return sum;
}
}
}
完全なテストコードはGitHubで入手できます:ReflectionTest.java