Faster alternatives to Java's reflection [closed]
Asked Answered
C

2

68

As we know, reflection is a flexible but slow method to maintain and modify the behaviour of the code at runtime.

But if we have to use such a functionality, are there any faster programming techniques in Java compared to Reflection API for dynamic modifications? What are pros and cons of these alternatives against reflection?

Chellean answered 24/10, 2013 at 5:40 Comment(7)
The main alternative is not manipulating and modifying code at runtime, which is generally quite fast.Siegfried
A rather tricky alternative would be to manipulate bytecode directly, see code.google.com/p/java-box-factory, asm.ow2.org, commons.apache.org/proper/commons-bcelOfay
What are you doing with reflection, and what is the current performance impact?Fleetwood
How slow is it for what you are doing? What is the difference between reflection and directly calling the methods instead? Don't assume that reflection is slow (it isn't in most cases in modern VMs)Silsbye
Are you referring to slow in terms of runtime performance or development time?Filamentary
I mean runtime performance.Chellean
Try: github.com/EsotericSoftware/reflectasmMaricruzmaridel
G
140

One alternative to Reflection is to generate a class file dynamically. This generated class ought to perform the desired action, e.g. invokes the method discovered at runtime, and implements an interface known at compile-time so that it’s possible to invoke the generated method in a non-reflective way using that interface. There’s one catch: if applicable, Reflection does the same trick internally. This does not work in special cases, e.g. when invoking a private method as you can’t generate a legal class file invoking it. So in the Reflection implementation there are different types of invocation handlers, using either generated code or native code. You can’t beat that.

But more important is that Reflection does security checks on every invocation. So your generated class will be checked on loading and instantiation only which can be a big win. Alternatively you can invoke setAccessible(true) on a Method instance to turn the security checks off. Then only the minor performance loss of autoboxing and varargs array creation remains.

Since Java 7 there is an alternative to both, the MethodHandle. The big advantage is that, unlike the other two, it even works in security restricted environments. The access checks for a MethodHandle are performed when acquiring it but not when invoking it. It has the so-called “polymorphic signature” which means you can invoke it with arbitrary argument types without auto-boxing nor array creation. Of course, wrong argument types will create an appropriate RuntimeException.

(Update) With Java 8, there is the option to use the back-end of the lambda expression and method reference language feature at runtime. This backend does exactly the thing described at the beginning, generating a class dynamically which implements an interface your code may call directly when it is known at compile-time. The exact mechanics is implementation-specific, hence undefined, but you can assume that the implementation will try it’s best to make the invocation as fast as possible. The current implementation of Oracle’s JRE does it perfectly. Not only that this saves you from the burden of generating such an accessor class, it is also capable of doing what you never could do— invoke even private methods via generated code. I have updated the example to include this solution. This example uses a standard interface which already exists and happens to have the desired method signature. If no such matching interface exists, you have to create your own accessor functional interface with a method with the right signature. But, of course, now the example code requires Java 8 to run.

Here is a simple benchmark example:

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.IntBinaryOperator;

public class TestMethodPerf
{
  private static final int ITERATIONS = 50_000_000;
  private static final int WARM_UP = 10;

  public static void main(String... args) throws Throwable
  {
 // hold result to prevent too much optimizations
    final int[] dummy=new int[4];

    Method reflected=TestMethodPerf.class
      .getDeclaredMethod("myMethod", int.class, int.class);
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh=lookup.unreflect(reflected);
    IntBinaryOperator lambda=(IntBinaryOperator)LambdaMetafactory.metafactory(
      lookup, "applyAsInt", MethodType.methodType(IntBinaryOperator.class),
      mh.type(), mh, mh.type()).getTarget().invokeExact();

    for(int i=0; i<WARM_UP; i++)
    {
      dummy[0]+=testDirect(dummy[0]);
      dummy[1]+=testLambda(dummy[1], lambda);
      dummy[2]+=testMH(dummy[1], mh);
      dummy[3]+=testReflection(dummy[2], reflected);
    }
    long t0=System.nanoTime();
    dummy[0]+=testDirect(dummy[0]);
    long t1=System.nanoTime();
    dummy[1]+=testLambda(dummy[1], lambda);
    long t2=System.nanoTime();
    dummy[2]+=testMH(dummy[1], mh);
    long t3=System.nanoTime();
    dummy[3]+=testReflection(dummy[2], reflected);
    long t4=System.nanoTime();
    System.out.printf("direct: %.2fs, lambda: %.2fs, mh: %.2fs, reflection: %.2fs%n",
      (t1-t0)*1e-9, (t2-t1)*1e-9, (t3-t2)*1e-9, (t4-t3)*1e-9);

    // do something with the results
    if(dummy[0]!=dummy[1] || dummy[0]!=dummy[2] || dummy[0]!=dummy[3])
      throw new AssertionError();
  }

  private static int testMH(int v, MethodHandle mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invokeExact(1000, v);
    return v;
  }

  private static int testReflection(int v, Method mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invoke(null, 1000, v);
    return v;
  }

  private static int testDirect(int v)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=myMethod(1000, v);
    return v;
  }

  private static int testLambda(int v, IntBinaryOperator accessor)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=accessor.applyAsInt(1000, v);
    return v;
  }

  private static int myMethod(int a, int b)
  {
    return a<b? a: b;
  }
}

Th old program printed in my Java 7 setup: direct: 0,03s, mh: 0,32s, reflection: 1,05s which suggested that MethodHandle was a good alternative. Now, the updated program running under Java 8 on the same machine printed direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s which clearly shows that Reflection performance has been improved to a degree that might make dealing with MethodHandle unnecessary, unless you use it to do the lambda trick, that clearly outperforms all reflective alternatives, which comes at no surprise, as it is just a direct call (well, almost: one level of indirection). Note that I made the target method private to demonstrate the capability of calling even private methods efficiently.

As always, I have to point at the simplicity of this benchmark and how artificial it is. But I think, the tendency is clearly visible and even more important, the results are convincingly explainable.

Gloriagloriana answered 24/10, 2013 at 10:10 Comment(17)
Hopefully, method handles get faster with Java 8.Adventurer
With the current beta, they are not. But, well, that’s beta. And they will be used by the lambda implementation, so there’s more pressure to improve it.Gloriagloriana
I think lambda way is a huge win but the example you provided works with static methods. Normally when I call invokeExact(instance, arguments) it works but since we want to compile the method as if it's a lambda expression, this keyword in method should not work theoretically. Do you know how to use lambda method for instance methods?Gallic
@Boyolame: see hereGloriagloriana
@Holger: Regarding the LambdaMetaFactory, do you know if the dynamically generated class will implement all of the interface’s methods? (It is not required to be a SAM). Or will it only implement the method with the specified signature (MethodType)?Thiol
@Hervian: it will implement the method which is specified by name and type. When you use the altMetafactory, you can specify type signatures of overloads to implement, but they still all have the same name (this is only to handle bridge methods). No other method will be implemented (it may override methods of java.lang.Object as this is allowed, but currently, it doesn’t).Gloriagloriana
FYI - I ran the same code with jdk1.8.0_102 and it showed the lambda is slightly always faster on my system. (in milliseconds) direct: 13.05ms, lambda: 12.34ms, mh: 133.98ms, reflection: 226.50msFiddlededee
@xer21: well, for differences in that magnitude you can blame the not very sophisticated benchmark method (if we dare to call it a benchmark at all). It’s only useful to show the tendency, i.e. greater magnitude which still is like “direct & lambda 10, mh 100, reflection 200”, though I’m surprised to see Reflection that bad in a recent JDK.Gloriagloriana
@Gloriagloriana Awesome snippet and explanation! I ran this on a Core i7-4600U 2.1Ghz CPU and got the following weird results: Java 8 (1.8.0_144) -> direct: 0.02s, lambda: 0.02s, mh: 0.18s, reflection: 0.31s, Java 7 (1.7.0_80) -> direct: 0.02s, mh: 0.32s, reflection: 12.32s. Do you have any clue why the reflective call would be so darn slow on Java 7? Thanks!Recollect
@geoand: the biggest obstacle is the fact that Method.invoke checks the caller’s permission on every invocation which the other approaches don’t. You may work-around this by calling reflected.setAccessible(true) right after acquiring the Method object. Besides that, there are boxing and varargs array creation. While the JVM’s optimizer should be capable of removing the overhead, it might depend on subtle environmental aspects whether it does for a particular call site.Gloriagloriana
@Gloriagloriana you could also add the use-case for IntBinaryOperator proxy = MethodHandleProxies.asInterfaceInstance(IntBinaryOperator.class, mh); for completenessGibrian
@Gibrian if the goal was getting an implementation of the interface, it would be an alternative, but here, the interface is only a tool for the performance goal and since the proxy still invokes the MethodHandle, it doesn’t make a difference.Gloriagloriana
I'm having trouble adapting this to a generic class, specifically Handler<RoutingContex> in Vert.x. The line MethodType.methodType(Handler.class) is non-generic, so I get: Lambda$100/0x000000080020f840 does not define or inherit an implementation of the resolved method abstract handle(Ljava/lang/Object;)V of interface io.vertx.core.Handler. -- i.e. the type-erased parameter type Object does not match the concrete parameter type RoutingContext.Also
@LukeHutchison you have to specify the erased signature ((Ljava/lang/Object;)V) as samMethodType parameter and the actual type signature ((Lio/vertx/ext/web/RoutingContext;)V) as instantiatedMethodType. Compare with LambdaMetafactory.metafactory(...). There are more Q&As regarding LambdaMetafactory on Stackverflow...Gloriagloriana
I don't really think that this lambda result is really good since this method is propably inlined at runtime.Hysterectomy
@Hysterectomy what’s wrong with method inlining?Gloriagloriana
@Gloriagloriana It replaces the invokeinterface or invokevirtual instruction with invokespecial. It might also inline the method. So in both the optimized result is the same thing. What I meant with it isn't good is that it isn't representable if there are multiple implementations of itHysterectomy
L
7

The alternate for reflection is using Interface. Just taking from Effective Java by Joshua Bloch.

We can obtain many of the benefits of reflection while incurring few of its costs by using it only in a very limited form. For many programs that must use a class that is unavailable at compile time, there exists at compile time an appropriate interface or superclass by which to refer to the class. If this is the case, you can create instances reflectively and access them normally via their interface or superclass. If the appropriate constructor has no parameters, then you don’t even need to use java.lang.reflect; the Class.newInstance method provides the required functionality.

Use reflection for only for creating the object i.e.

// Reflective instantiation with interface access
   public static void main(String[] args) {
       // Translate the class name into a Class object
       Class<?> cl = null;
       try {
           cl = Class.forName(args[0]);
       } catch(ClassNotFoundException e) {
           System.err.println("Class not found.");
           System.exit(1);
       }
       // Instantiate the class
       Set<String> s = null;
       try {
           s = (Set<String>) cl.newInstance();
       } catch(IllegalAccessException e) {
           System.err.println("Class not accessible.");
           System.exit(1);
       } catch(InstantiationException e) {
           System.err.println("Class not instantiable.");
           System.exit(1);
       }
       // Exercise the set
       s.addAll(Arrays.asList(args).subList(1, args.length));
       System.out.println(s);
}

While this program is just a toy, the technique it demonstrates is very powerful. The toy program could easily be turned into a generic set tester that validates the specified Set implementation by aggressively manipulating one or more instances and checking that they obey the Set contract. Similarly, it could be turned into a generic set performance analysis tool. In fact, the technique is sufficiently powerful to implement a full-blown service provider framework . Most of the time, this technique is all that you need in the way of reflection.

This example demonstrates two disadvantages of reflection. First, the example can generate three runtime errors, all of which would have been compile-time errors if reflective instantiation were not used. Second, it takes twenty lines of tedious code to generate an instance of the class from its name, whereas a con- structor invocation would fit neatly on a single line. These disadvantages are, however, restricted to the part of the program that instantiates the object. Once instantiated, it is indistinguishable from any other Set instance.

Littman answered 24/10, 2013 at 10:18 Comment(1)
In the 3rd ed. of "Effective Java" this is "Item 65: Prefer interfaces to reflection”.Immaterial

© 2022 - 2024 — McMap. All rights reserved.