📜 ⬆️ ⬇️

Make reflection fast as direct calls.

Most programmers are aware of reflection , which (it’s a reflection) makes it easy to add dynamic capabilities to static languages ​​such as Java / C #. However, reflection is blamed for the fact that calls work very slowly - up to 500 times slower. Nevertheless, this can be easily corrected - we will show in this article how to make a reflection-call as fast as a direct call.


Measure the speed of the next class:

Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }
  1. Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }
  2. Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }
  3. Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }
  4. Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }
  5. Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }
  6. Copy Source | Copy HTML class A { public int value = 0 ; public void add( int x) { value += x; } }

')
I measured the speed on a Core 2 Duo E6300, Windows Vista, JRE 1.6.0_16. The Timer class is an auxiliary class for measuring time. For the measurement we will make 5 million calls of each method. Let's start with direct access to the class field:

Copy Source | Copy HTML
  1. t.start ( "Direct field access" );
  2. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  3. a. value + = i;
  4. }
  5. t.stop ();


5000000 iterations performed in 44ms. Time may vary from call to call, but in general, the result is this. Let's do the same with the use of reflection:

Copy Source | Copy HTML
  1. t.start ( "Preparing for reflective field access" );
  2. Field f = A. class .getField ( "value" );
  3. t.stop ();
  4. t.start ( "Reflective field access" );
  5. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  6. f. set (a, ((Integer) f. get (a)) + i);
  7. }
  8. t.stop ();


Run for 11233ms - more than 250 times slower! Repeat the same thing, replacing direct work with fields on method calls.

Copy Source | Copy HTML
  1. t.start ( "Direct method access" );
  2. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  3. a.add (i);
  4. }
  5. t.stop ();
  6. t.start ( "Preparing for reflective method access" );
  7. Method m = A. class. GetDeclaredMethod ( "add" , int . Class );
  8. t.stop ();
  9. t.start ( "Reflective method access" );
  10. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  11. m.invoke (a, i);
  12. }
  13. t.stop ();


A direct method call is slightly slower than direct access to the field and is performed in 51 ms. At the same time, the reflection-access to the method is slightly faster - 4177 ms - about 100 slower than direct access.

How to optimize it?


Reflection by means of JVM is implemented using slow JNI calls. CGLIB provides the FastMethod class, which provides the same functionality without using JNI, making calls much, much faster:

Copy Source | Copy HTML
  1. t.start ( "Preparing for fast reflective method access" );
  2. FastClass fc = FastClass.create (A. Class );
  3. FastMethod fm = fc.getMethod (m);
  4. t.stop ();
  5. t.start ( "Fast reflective method access" );
  6. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  7. fm.invoke (a, new Object [] {i});
  8. }
  9. t.stop ();


The result is 353 ms, which is only 7 times slower than the usual method call. If CGLIB had an analogue FastMethod for fields, we could compare and access fields.

Let's think about how you can achieve better results in the previous snippet. We have several costly operations: the first is to create an array in a loop, we can do with one array for all iterations of the loop. The other is boxing (auto-boxing) numbers. Suppose that a number is not a number, but an ordinary object and does not require boxing:

Copy Source | Copy HTML
  1. class Ref {
  2. int value ;
  3. }
  4. class A {
  5. public int value = 0 ;
  6. public void add ( Ref ref ) {
  7. value + = ref . value ;
  8. }
  9. }
  10. ...
  11. t.start ( "Preparing for fast reflective method access (2)" );
  12. FastClass fc2 = FastClass.create ( A. Class );
  13. FastMethod fm2 = fc2.getMethod ( "add" , new Class [] { Ref . Class });
  14. Ref ref = new Ref ();
  15. Object [] arguments = new Object [] { ref };
  16. t.stop ();
  17. t.start ( "Fast reflective method access (2)" );
  18. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  19. ref . value = i;
  20. fm2.invoke (a, arguments);
  21. }
  22. t.stop ();


The result is 112ms. Only 2 times slower than the usual method call! However, why slower? The fact is that FastMethod is implemented using the Decorator design pattern . Let's try to decorate our class A and evaluate the result:

Copy Source | Copy HTML
  1. interface AIf {
  2. public void add ( int x);
  3. }
  4. class A implements AIf {
  5. public int value = 0 ;
  6. public void add ( int x) {
  7. value + = x;
  8. }
  9. }
  10. class ADecorator implements AIf {
  11. private AIf a;
  12. public ADecorator ( AIf a) {
  13. this .a = a;
  14. }
  15. public void add ( int x) {
  16. a.add (x);
  17. }
  18. }
  19. ...
  20. t.start ( "Preparing decorator method access" );
  21. AIf d = new ADecorator (a);
  22. t.stop ();
  23. t.start ( "Decorator method access" );
  24. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  25. d.add (i);
  26. }
  27. t.stop ();


Run for 124 ms - it turned out even slower than FastMethod. And since every good programmer uses design patterns, we can say that with some optimization, reflection can be as fast as direct calls without reflection.

Full listing:

Copy Source | Copy HTML
  1. package reflection;
  2. import net.sf.cglib.reflect.FastClass;
  3. import net.sf.cglib.reflect.FastMethod;
  4. import java.lang.reflect.Field;
  5. import java.lang.reflect.InvocationTargetException;
  6. import java.lang.reflect.Method;
  7. import java.util. LinkedHashMap ;
  8. import java.util.Map;
  9. class Timer {
  10. private long startTime = 0 ;
  11. private String msg = null ;
  12. private Map < String , Long > map = new LinkedHashMap < String , Long > ();
  13. public void start ( String msg) {
  14. if (startTime! = 0 ) {
  15. throw new IllegalStateException ( "Already started" );
  16. }
  17. startTime = System.currentTimeMillis ();
  18. this .msg = msg;
  19. }
  20. public void stop () {
  21. if (startTime == 0 ) {
  22. throw new IllegalStateException ( "Not started" );
  23. }
  24. long now = System.currentTimeMillis ();
  25. Long n = map. get (msg);
  26. if (n == null ) {
  27. n = 0l;
  28. }
  29. n + = (now - startTime);
  30. map.put (msg, n);
  31. startTime = 0 ;
  32. msg = null ;
  33. }
  34. public void output () {
  35. for ( String msg: map.keySet ()) {
  36. System. out .println (msg + ":" + map. get (msg));
  37. }
  38. }
  39. }
  40. class Ref {
  41. int value ;
  42. }
  43. interface AIf {
  44. public void add ( int x);
  45. }
  46. class A implements AIf {
  47. public int value = 0 ;
  48. public void add ( int x) {
  49. value + = x;
  50. }
  51. public void add ( Ref ref ) {
  52. value + = ref . value ;
  53. }
  54. }
  55. class ADecorator implements AIf {
  56. private AIf a;
  57. public ADecorator ( AIf a) {
  58. this .a = a;
  59. }
  60. public void add ( int x) {
  61. a.add (x);
  62. }
  63. }
  64. public class Reflect {
  65. private static final int TOTAL_LOOP_COUNT = 5000000 ;
  66. / ** <br/> * How many loops to do in one step. <br/> * /
  67. private static final int LOOPS_IN_STEP_COUNT = 100 ;
  68. / ** <br/> * How many steps to do in each step there will be <br/> * {@link #LOOPS_IN_STEP_COUNT} loops. <br/> * /
  69. private static final int STEP_COUNT = TOTAL_LOOP_COUNT / LOOPS_IN_STEP_COUNT;
  70. public static void main ( String [] args) throws SecurityException,
  71. NoSuchFieldException, IllegalArgumentException,
  72. IllegalAccessException, NoSuchMethodException,
  73. InvocationTargetException {
  74. Timer t = new Timer ();
  75. A a = new A ();
  76. for ( int j = 0 ; j <STEP_COUNT; ++ j) {
  77. t.start ( "Direct field access" );
  78. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  79. a. value + = i;
  80. }
  81. t.stop ();
  82. t.start ( "Preparing for reflective field access" );
  83. Field f = A. class .getField ( "value" );
  84. t.stop ();
  85. t.start ( "Reflective field access" );
  86. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  87. f. set (a, ((Integer) f. get (a)) + i);
  88. }
  89. t.stop ();
  90. t.start ( "Direct method access" );
  91. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  92. a.add (i);
  93. }
  94. t.stop ();
  95. t.start ( "Preparing for reflective method access" );
  96. Method m = A. class .getDeclaredMethod ( "add" , int . class );
  97. t.stop ();
  98. t.start ( "Reflective method access" );
  99. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  100. m.invoke (a, i);
  101. }
  102. t.stop ();
  103. t.start ( "Preparing for fast reflective method access" );
  104. FastClass fc = FastClass.create ( A. Class );
  105. FastMethod fm = fc.getMethod (m);
  106. t.stop ();
  107. t.start ( "Fast reflective method access" );
  108. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  109. fm.invoke (a, new Object [] {i});
  110. }
  111. t.stop ();
  112. t.start ( "Preparing for fast reflective method access (2)" );
  113. FastClass fc2 = FastClass.create ( A. Class );
  114. FastMethod fm2 = fc2.getMethod ( "add" , new Class [] {
  115. Ref . class
  116. });
  117. Ref ref = new Ref ();
  118. Object [] arguments = new Object [] { ref };
  119. t.stop ();
  120. t.start ( "Fast reflective method access (2)" );
  121. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  122. ref . value = i;
  123. fm2.invoke (a, arguments);
  124. }
  125. t.stop ();
  126. t.start ( "Preparing decorator method access" );
  127. AIf d = new ADecorator (a);
  128. t.stop ();
  129. t.start ( "Decorator method access" );
  130. for ( int i = 0 ; i <LOOPS_IN_STEP_COUNT; ++ i) {
  131. d.add (i);
  132. }
  133. t.stop ();
  134. }
  135. t.output ();
  136. }
  137. }


And the result:

Direct field access: 44
Preparing for reflective field access: 765
Reflective field access: 11233
Direct method access: 51
Preparing for reflective method access: 1020
Reflective method access: 4177
Preparing for fast reflective method access: 1400
Fast reflective method access: 353
Preparing for fast reflective method access (2): 2164
Fast reflective method access (2): 112
Preparing decorator method access: 52
Decorator method access: 124

You may notice that the “preparation” takes some time, but this operation can be performed only once. “Preparation” of standard methods of reflection takes less than 1 ms, methods of CGLIB - about 100 ms. The point is that the generated class must be compiled and loaded by the class loader . However, such generation can be performed only once.

So, reflection in Java is not a bottleneck when using FastMethod from the CGLIB library. And after some optimizations, calls can be as fast as immediate calls. Now you can not be afraid to use these features in your code. However, it is important to remember that premature optimization leads to problems, and you need to optimize only the code that really is the bottleneck of your application.

Translator's note: I increased the number of iterations 10 times and ran (Intel Q6600, Vista x64, JRE 1.6.0_16 x64) the test cited in the article, obtained several other results - “decoration” had practically no effect on the time of 120 vs 168, and the final result turned out to be 6 times slower than a direct call, not 2 as in the article:

Direct field access: 170
Preparing for reflective field access: 1370
Reflective field access: 33928
Direct method access: 120
Preparing for reflective method access: 3382
Reflective method access: 21208
Preparing for fast reflective method access: 3043
Fast reflective method access: 1247
Preparing for fast reflective method access (2): 13174
Fast reflective method access (2): 741
Preparing decorator method access: 128
Decorator method access: 168

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


All Articles