Faster alternatives to Java's reflection
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:
- static calls and instance calls
- Access to private methods, and methods from other packages
- '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.
ovunccetin
Updated on July 08, 2022Comments
-
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 over 10 yearsHopefully, method handles get faster with Java 8.
-
Holger over 10 yearsWith 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 over 9 yearsI 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 over 9 years@Boyolame: see here
-
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 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 ofjava.lang.Object
as this is allowed, but currently, it doesn’t). -
xer21 over 7 yearsFYI - 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 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 about 7 yearsChecked your code, not able to compile your code, Lamba - cannot be resolved to a type.
-
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 about 7 yearsTried 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 about 7 yearsRan 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 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 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 callingreflected.setAccessible(true)
right after acquiring theMethod
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 about 6 years@Holger you could also add the use-case for IntBinaryOperator proxy = MethodHandleProxies.asInterfaceInstance(IntBinaryOperator.class, mh); for completeness
-
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 over 5 yearsI'm having trouble adapting this to a generic class, specifically
Handler<RoutingContex>
in Vert.x. The lineMethodType.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 typeObject
does not match the concrete parameter typeRoutingContext
. -
Holger over 5 years@LukeHutchison you have to specify the erased signature (
(Ljava/lang/Object;)V
) assamMethodType
parameter and the actual type signature ((Lio/vertx/ext/web/RoutingContext;)V
) asinstantiatedMethodType
. Compare withLambdaMetafactory.metafactory(...)
. There are more Q&As regardingLambdaMetafactory
on Stackverflow... -
sb27 over 4 yearsI don't really think that this lambda result is really good since this method is propably inlined at runtime.
-
Holger over 4 years@sb27 what’s wrong with method inlining?
-
sb27 over 4 years@Holger It replaces the
invokeinterface
orinvokevirtual
instruction withinvokespecial
. 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 over 2 yearsIn the 3rd ed. of "Effective Java" this is "Item 65: Prefer interfaces to reflection”.