最近、作業中のプロジェクトのコードを調べてみると、リフレクションを介してオブジェクトゲッターとセッターを呼び出すオブジェクト(オンザフライで生成されたクラスのオブジェクト)のメソッドを呼び出す、かなり負荷の高いSpring Beanに出会いました。 ゲッターキャッシュはすでにBeanに実装されていますが、リフレクションの速度と、リフレクションを高速化できるかどうか疑問に思いました。

手っ取り早く、メソッドを呼び出すさまざまなメソッドのパフォーマンスを測定するマイクロベンチマークがJMHで作成されました。 マイクロベンチマークを書くプロセスは恩知らずの問題です;間違いを犯し、あなたが望むものと完全に異なる何かを測定するための無数の方法があります。 そのため、ボクシングボクシングを見落とし、その結果、ベンチマークの最初のバージョンでは、メソッド呼び出し自体ではなく、それを測定しました。 そして、PrintAssemblyを見て初めて間違いを見つけました。
結果は興味深いことが判明しましたが、リフレクションを介してメソッド呼び出しを直接比較する記事がすでにハブにありました。したがって、結果を見て、より良い時間までそれらをボックスに入れようとしましたが、突然、政治で満たされたtwitterフィードはjava8リリースに関するツイートで希釈されました 私は喜びを利用して、JDK7とJDK8のリフレクションのパフォーマンスを比較することにしました。
JMHを適切にベンチマークするためのフレームワークの結果の表記について簡単に説明します。
- ベンチマーク-@GenerateMicroBenchmarkとマークされたメソッドの名前
- モード-ベンチマークモード、私の場合はthrpt-スループット、一定期間の操作数-私の場合は1秒
- サンプル-測定数
- 平均-指定された期間に実行された操作の平均数
- 平均誤差-標準誤差
- 単位-測定単位-私の場合の操作/秒
最初に測定したのは、クラスのフィールドに直接アクセスすることです。
- testFieldSaveAccessible-フィールドでsetAccessible(true)を呼び出してフィールドにアクセスします
- testFieldSaveNotAccessible-上げられたフィールドを介してアクセスするだけです
- testFieldStraighforward-メソッド呼び出しによる直接アクセス
静的フィールドについても同様です。
フィールドアクセステスト
@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ReflectionFieldAccess { private static final Class<TestedClass> clazz = TestedClass.class; private TestedClass testedObject; Field simpleField; Field fieldAccessible; @Setup public void init() { try { testedObject = new TestedClass(); simpleField = clazz.getField("a"); Field Field = clazz.getField("b"); Field.setAccessible(true); fieldAccessible = Field; } catch (Exception e) { // do nothing } } @GenerateMicroBenchmark public Object testFieldSaveAccessible() throws Exception { return fieldAccessible.get(testedObject); } @GenerateMicroBenchmark public Object testFieldSaveNotAccessible() throws Exception { return simpleField.get(testedObject); } @GenerateMicroBenchmark public Object testFieldStraighforward() throws Exception { return testedObject.c; } }
静的フィールドへのアクセスを測定するテスト
@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ReflectionFieldStaticAccess { private static final Class<TestedClass> clazz = TestedClass.class; Field simpleField; Field fieldAccessible; @Setup public void init() { try { simpleField = clazz.getField("aStat"); Field Field = clazz.getField("bStat"); Field.setAccessible(true); fieldAccessible = Field; } catch (Exception e) { // do nothing } } @GenerateMicroBenchmark public Object testFieldSaveAccessible() throws Exception { return fieldAccessible.get(null); } @GenerateMicroBenchmark public Object testFieldSaveNotAccessible() throws Exception { return simpleField.get(null); } @GenerateMicroBenchmark public Object testFieldStraighforward() throws Exception { return TestedClass.cStat; } }
JDK7の結果:

JDK8の結果:

比較結果:


実際、結果は非常に期待されています。
- setAccessible(true)を設定すると、権限の検証が不要になるため、パフォーマンスが向上します。
- オブジェクトフィールドへの直接アクセスは、リフレクションを介したアクセスよりも約2倍高速です
- 興味深いことに、リフレクションによるアクセスパフォーマンスはjdk8で改善されています
メソッド呼び出しの結果の比較に移りましょう。ここでは、調査中のツールの選択肢が大幅に増えています。
jdk7で利用可能な、JSR 292の一部であるMethodHandle APIを使用するための最後の2つのテスト。
- testFastMethod-CGLIBのFastMethodを使用したメソッド呼び出し
- testMethodNotAccessible-リフレクションによる単純な呼び出し
- testMethodAccessible-MethodでのsetAccessible(true)の呼び出しを伴うリフレクションによる呼び出し
- testMethodHandle-MethodHandle.invokeを呼び出す
- testMethodHandleExact-正確な型の一致を必要とするMethodHandle.invokeExactの呼び出し
静的メソッドの場合も同様です。
メソッドへのアクセスを測定するテスト
@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ReflectionMethodAccess { private static final Class<TestedClass> clazz = TestedClass.class; private TestedClass testedObject; Method simpleMethod; Method methodAccessible; FastMethod fastMethod; MethodHandle methodHandle; @Setup public void init() { try { testedObject = new TestedClass(); simpleMethod = clazz.getMethod("getA", null); Method method = clazz.getMethod("getB", null); method.setAccessible(true); methodAccessible = method; fastMethod = FastClass.create(clazz).getMethod("getC", null); methodHandle = MethodHandles.lookup().findVirtual(clazz, "getD", MethodType.methodType(Integer.class)); } catch (Exception e) { // do nothing } } @GenerateMicroBenchmark public Object testFastMethod() throws Throwable { return fastMethod.invoke(testedObject, null); } @GenerateMicroBenchmark public Object testMethodAccessible() throws Throwable { return methodAccessible.invoke(testedObject, null); } @GenerateMicroBenchmark public Object testMethodNotAccessible() throws Throwable { return simpleMethod.invoke(testedObject, null); } @GenerateMicroBenchmark public Object testMethodHandleExact() throws Throwable { return (Integer)methodHandle.invokeExact(testedObject); } @GenerateMicroBenchmark public Object testMethodHandle() throws Throwable { return (Integer)methodHandle.invoke(testedObject); } @GenerateMicroBenchmark public Object testMethodDirect() throws Throwable { return testedObject.getA(); } }
静的メソッドへのアクセスを測定するテスト
@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) @State(Scope.Thread) public class ReflectionMethodStaticAccess { private static final Class<TestedClass> clazz = TestedClass.class; Method simpleMethod; Method methodAccessible; MethodHandle methodHandle; FastMethod fastMethod; @Setup public void init() { try { simpleMethod = clazz.getMethod("getAStatic", null); Method method = clazz.getMethod("getBStatic", null); method.setAccessible(true); methodAccessible = method; fastMethod = FastClass.create(clazz).getMethod("getCStatic", null); methodHandle = MethodHandles.lookup().findStatic(clazz, "getDStatic", MethodType.methodType(Integer.class)); } catch (Exception e) { // do nothing } } @GenerateMicroBenchmark public Object testFastMethod() throws Throwable { return fastMethod.invoke(null, null); } @GenerateMicroBenchmark public Object testMethodAccessible() throws Throwable { return methodAccessible.invoke(null, null); } @GenerateMicroBenchmark public Object testMethodNotAccessible() throws Throwable { return simpleMethod.invoke(null, null); } @GenerateMicroBenchmark public Object testMethodHandleExact() throws Throwable { return (Integer)methodHandle.invokeExact(); } @GenerateMicroBenchmark public Object testMethodHandle() throws Throwable { return (Integer)methodHandle.invoke(); } @GenerateMicroBenchmark public Object testMethodDirect() throws Throwable { return TestedClass.getAStatic(); } }
MethodHandleの詳細については、たとえば、 vladimir Ivanovによるinvokedynamicsに関するレポートを参照してください。
JDK7の結果:

JDK8の結果:

比較結果:


グラフから、いくつかの結論が得られます。
- 未知の理由により、静的メソッドのFastMethodはjdk7でゆっくりと動作し、jdk8では2倍高速に動作します-setAccessible(true)を使用した方法と同様(差は誤差範囲内です)
- jdk8では、MethodHandle.invokeの動作は非常に最適化されています。これは確かにラムダによるものです
- フィールドの場合と同様に、全体的な反射性能が向上しました。
実際、プロジェクトでリフレクションを使用する場合、結論は簡単です。次に、jdk8に切り替える追加の理由を示します。
ベンチマークを試してみたい場合は、アーキテクチャのリフレクションパフォーマンスを測定するか、バグを探してから、githubへようこそ。
PS結果に影響を与えた特定の効果を説明できる専門家にコメントできてうれしいです。