Faster alternatives to Java's reflection

51,714

Solution 1

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.

Solution 2

I have created a small library called lambda-factory. It is based on LambdaMetafactory, but saves you the hassle of finding or creating an interface that matches the method.

Here are some sample runtimes for 10E8 iterations (reproducable with the class PerformanceTest):

Lambda: 0.02s, Direct: 0.01s, Reflection: 4.64s for method(int, int)
Lambda: 0.03s, Direct: 0.02s, Reflection: 3.23s for method(Object, int)

Let's say we have a class called MyClass, which defines the following methods:

private static String myStaticMethod(int a, Integer b){ /*some logic*/ }
private float myInstanceMethod(String a, Boolean b){ /*some logic*/ }

We can access these methods like this:

Method method = MyClass.class.getDeclaredMethod("myStaticMethod", int.class, Integer.class); //Regular reflection call
Lambda lambda = LambdaFactory.create(method);  
String result = (String) lambda.invoke_for_Object(1000, (Integer) 565); //Don't rely on auto boxing of arguments!

Method method = MyClass.class.getDeclaredMethod("myInstanceMethod", String.class, Boolean.class);
Lambda lambda = LambdaFactory.create(method);
float result = lambda.invoke_for_float(new MyClass(), "Hello", (Boolean) null);  //No need to cast primitive results!

Notice that when invoking the lambda, you must choose an invocation method that contains the target method's return type in its name. - varargs and auto boxing were too expensive.

In the example above, the chosen invoke_for_float method indicates that we are invoking a method, which returns a float. If the method you are trying to access returns fx a String, a boxed primitive (Integer, Boolean etc) or some custom Object, you would call invoke_for_Object.

The project is a good template for experimenting with LambdaMetafactory since it contains working code for various aspects:

  1. static calls and instance calls
  2. Access to private methods, and methods from other packages
  3. 'invokeSpecial' logic, i.e. where the created implementation is such, that it bypasses dynamic method dispatch.

Solution 3

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.

Share:
51,714
ovunccetin
Author by

ovunccetin

Updated on July 08, 2022

Comments

  • ovunccetin
    ovunccetin almost 2 years

    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?

  • Ingo
    Ingo over 10 years
    Hopefully, method handles get faster with Java 8.
  • Holger
    Holger over 10 years
    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.
  • Boyolame
    Boyolame over 9 years
    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?
  • Holger
    Holger over 9 years
    @Boyolame: see here
  • Hervian
    Hervian about 8 years
    @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)?
  • Holger
    Holger about 8 years
    @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).
  • xer21
    xer21 over 7 years
    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.50ms
  • Holger
    Holger over 7 years
    @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.
  • aryanRaj_kary
    aryanRaj_kary about 7 years
    Checked your code, not able to compile your code, Lamba - cannot be resolved to a type.
  • Hervian
    Hervian about 7 years
    @aryanRaj_kary It Works fine for me. You question is answered on the referenced github project, under the paragraph "Building from source". Brief summary: You must A) run "mvn clean install" and B) Instruct your IDE to include the generated source file. Alternatively and much simpler you just download the jar file and use that (Click on the 'releases' tab)
  • aryanRaj_kary
    aryanRaj_kary about 7 years
    Tried the jar, but getting this exception: java.lang.NoSuchFieldException: allowedModes Method method = TestSACP.class.getDeclaredMethod("getData", String.class); Lambda lambda = LambdaFactory.create(method); String result = (String)lambda.invoke_for_Object(new TestSACP(), "hello"); public class TestSACP{ private String getData(String a){ return "from SACP" +a; } }
  • Hervian
    Hervian about 7 years
    Ran your code with LambdaFactory on the classpath and it works for me. My best guess is that the NoSuchFieldException you report comes from another part of your application. 'public class Main { public static void main(String... args) throws Throwable{ Method method = Main.class.getDeclaredMethod("getData", String.class); Lambda lambda = LambdaFactory.create(method); String result = (String)lambda.invoke_for_Object(new Main(), "hello"); System.out.println("Success2"); } private String getData(String a){ System.out.println("Success1"); return "from SACP" +a; } }'
  • geoand
    geoand over 6 years
    @Holger 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!
  • Holger
    Holger over 6 years
    @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.
  • adamretter
    adamretter about 6 years
    @Holger you could also add the use-case for IntBinaryOperator proxy = MethodHandleProxies.asInterfaceInstance(IntBinaryOperator.cl‌​ass, mh); for completeness
  • Holger
    Holger about 6 years
    @adamretter 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.
  • Luke Hutchison
    Luke Hutchison over 5 years
    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.
  • Holger
    Holger over 5 years
    @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...
  • sb27
    sb27 over 4 years
    I don't really think that this lambda result is really good since this method is propably inlined at runtime.
  • Holger
    Holger over 4 years
    @sb27 what’s wrong with method inlining?
  • sb27
    sb27 over 4 years
    @Holger 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 it
  • MiB
    MiB over 2 years
    In the 3rd ed. of "Effective Java" this is "Item 65: Prefer interfaces to reflection”.