クラスコンストラクターを呼び出すのではなく、リフレクションを使用してオブジェクトを作成すると、パフォーマンスに大きな違いが生じますか?
はい-絶対に。リフレクションを介してクラスを検索することは、等級で、より高価です。
リフレクションには動的に解決される型が含まれるため、特定の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ルールを忘れないでください。PREMATURE最適化ISすべての悪の根源
(まあ、DRYの場合は#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の差があることを示しています。
リフレクションよりも高速な処理が必要なreallyがあり、それが早すぎる最適化ではない場合、 ASM を使用したバイトコード生成=または、より高いレベルのライブラリがオプションです。最初にバイトコードを生成することは、リフレクションを使用するよりも遅くなりますが、バイトコードが生成されると、通常のJavaコードと同じくらい速くなり、JITコンパイラーによって最適化されます。
コード生成を使用するアプリケーションの例:
CGLIB で生成されたプロキシでメソッドを呼び出すと、Javaの 動的プロキシ よりもわずかに高速になります。これは、CGLIBがプロキシのバイトコードを生成しますが、動的プロキシはリフレクション( 私は測定しました CGLIBはメソッド呼び出しで約10倍速くなりましたが、プロキシの作成は遅くなりました)。
JSerial は、リフレクションを使用する代わりに、シリアル化されたオブジェクトのフィールドを読み書きするためのバイトコードを生成します。 JSerialのサイトには ベンチマーク があります。
私は100%確信はありません(そして今ソースを読む気はありません)が、私は Guice が依存性注入を行うためのバイトコードを生成すると思います。私が間違っている場合は修正してください。
「重要」は完全に文脈に依存しています。
リフレクションを使用して、構成ファイルに基づいて単一のハンドラーオブジェクトを作成し、残りの時間をデータベースクエリの実行に費やしている場合は、意味がありません。タイトなループ内のリフレクションを介して多数のオブジェクトを作成している場合、それは重要です。
一般に、設計の柔軟性(必要な場合)は、パフォーマンスではなく反射の使用を促進するはずです。ただし、パフォーマンスが問題であるかどうかを判断するには、ディスカッションフォーラムから任意の応答を取得するのではなく、プロファイルを作成する必要があります。
リフレクションには多少のオーバーヘッドがありますが、最新のVMでは以前よりはるかに小さくなっています。
プログラム内のすべての単純なオブジェクトを作成するためにリフレクションを使用している場合、何かが間違っています。あなたが正当な理由があるとき、それを時折使用することは全く問題ではないはずです。
オブジェクトの割り当ては、リフレクションの他の側面ほど絶望的ではありませんが、リフレクションは遅いです。リフレクションベースのインスタンス化で同等のパフォーマンスを達成するには、jitがどのクラスがインスタンス化されているかを知ることができるようにコードを記述する必要があります。クラスのIDを特定できない場合、割り当てコードをインライン化できません。さらに悪いことに、エスケープ分析は失敗し、オブジェクトをスタックに割り当てることはできません。運がよければ、このコードが熱くなるとJVMの実行時プロファイリングが助けになり、どのクラスが優勢かを動的に決定し、そのクラスに対して最適化することができます。
このスレッドのマイクロベンチマークには深い欠陥があることに注意してください。一番欠点があるのはPeter Lawreyの方法です。ウォームアップを実行してメソッドを取得し、エスケープ分析を(意識的に)無効にして、割り当てが実際に行われていることを確認します。ただし、その1つにも問題があります。たとえば、膨大な数の配列ストアがキャッシュとストアバッファーを無効にすることが予想されるため、割り当てが非常に高速な場合、これは主にメモリベンチマークになります。 (しかし、正しい結論を得たピーターへの称賛:その差は「2.5x」ではなく「150ns」である。彼はこの種のことを生計のために行っていると思う。)
はい。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ミリ秒かかりました
はい、かなり遅いです。私たちはそれを実行するコードを実行していましたが、現時点ではメトリックを利用できませんが、最終結果はリフレクションを使用しないようにそのコードをリファクタリングする必要がありました。クラスが何かを知っている場合は、コンストラクタを直接呼び出してください。
興味深いことに、セキュリティチェックをスキップする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
DoReflection()には、クラスで呼び出されるnewInstance()ではなく、Class.forName( "misc.A")(潜在的にfilsystemのクラスパスをスキャンする必要があるクラス検索)のためのオーバーヘッドがあります。 Class.forName( "misc.A")がforループの外側で1回だけ実行された場合、統計はどのように表示されるのか疑問に思います。ループの呼び出しごとに実行する必要はありません。
はい、JVMはコンパイル時にコードを最適化できないため、リフレクションによるオブジェクトの作成は常に遅くなります。詳細については、Sun/Java Reflection tutorials を参照してください。
次の簡単なテストをご覧ください。
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");
}
}
多くの場合、イントロスペクションのApache commons 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