📜 ⬆️ ⬇️

Compare reflection performance in JDK8 and JDK7

Hi, Habr!

Recently, traveling through the code of my working project, I came across a fairly high-loaded spring bin, which made calls to the methods of objects (sometimes also objects generated on the fly of classes) calling object getters and setters through reflection. The cache of getters has already been implemented in bin, but I wondered how fast the reflection could be and whether it could be done faster.



')
With a quick hand, a microbench was written on JMH, which measures the performance of various methods of calling methods. The process of writing a microbenchmark is a thankless task, there are a million ways to make a mistake and measure something different from what I wanted. So I lost boxing-boxing out of my head and as a result, in the first version of the benchmark I measured it, and not the method call itself. And I found my mistake only when I looked at PrintAssembly.

The results were interesting, but there were already articles in Habré that compared the call of methods through reflection and directly, so looking at the results, I was going to put them in a box until better times, but suddenly the twitter feed filled with the policy was diluted with java8 tweets. Having curbed my joy, I decided to compare the performance of reflection in JDK7 and JDK8.

Briefly about the notation in the results of the framework for the correct JMH benchmarking:


The first thing I measured was access to the class fields directly:

Similarly for static fields.

A test that measures field access
@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; } } 


Test Measuring Access to Static Fields
 @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; } } 



Results for JDK7:


Results for JDK8:


Results in comparison:



Actually, the results are quite expected:
  1. Putting setAccessible (true) gives us a performance boost due to the absence of the need for rights verification
  2. Access to the object's fields directly is about 2 times faster than access via reflection
  3. It is interesting that in jdk8 access performance through reflection is improved


Let us turn to a comparison of the results for the method calls, here we have a much larger selection of the investigated means.
The last two tests for using the API MethodHandle, part of JSR 292, available from jdk7.

Similarly for static methods.

Test Measuring Access to Methods
 @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(); } } 


Test Measuring Access to Static Methods
 @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(); } } 



More information about MethodHandle can be heard, for example, in the report of Vladimir Ivanov about invokedynamics

Results for JDK7:


Results for JDK8:


Results in comparison:



From the graphs we can draw several conclusions:
  1. For reasons unknown to me, FastMethod for static methods worked slowly on jdk7, while on jdk8 it works 2 times faster - similar to the method with setAccessible (true) (difference within the error)
  2. In jdk8, MethodHandle.invoke is very optimized, for sure it is connected with lambdas
  3. The overall performance of reflection has grown, as in the case of fields


Actually the conclusion is simple if you use in your project reflection - then here's another reason to go to jdk8.

If you want to play with the benchmark, measure the performance of reflection on your architecture or look for errors, then welcome to github .

PS I would be happy to comment on experts who can explain certain effects that influenced the result.

Source: https://habr.com/ru/post/216435/


All Articles