给定一个类Foo
和属性bar
, 这两者都不知道在编译的时候 ,我需要反复调用的getter Foo.getBar()
很多很多次。
假设我有:
Method barGetterMethod = ...; // Don't worry how I got this
我需要做这样的事情:
for (Object foo : fooList) { // 1000000000 elements in fooList
Object bar = barGetterMethod.invoke(foo);
...
}
上面的实现仍然非常缓慢相比,称其没有反映。 是否有一个更快的方法?
什么是呼叫信息与Java反射吸气的最快的方法?
您可以使用一个MethodHandle 。 它的Javadoc写道:
使用查找API在工厂方法,由核心映像API对象表示任何类成员可以被转化成一个等效行为上手柄方法。 例如,反射的方法可使用Lookup.unreflect被转换为一个方法处理。 将得到的方法处理一般提供到底层类成员更直接和高效的访问。
尽管这将降低开销,方法处理仍然防止某些优化(这种方法内联)如果呼叫是与通常的(非反射性的)字节代码指令进行的JVM可以采用。 无论这种优化将有利于取决于你如何使用此方法(如果代码路径总是调用同样的方法,内联可以帮助,如果它是一个不同的方法,每一次,可能不会)。
下面的微基准测试可能给你反映,方法处理,并直接调用的相对性能粗略的想法:
package tools.bench;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.math.BigDecimal;
public abstract class Bench {
final String name;
public Bench(String name) {
this.name = name;
}
abstract int run(int iterations) throws Throwable;
private BigDecimal time() {
try {
int nextI = 1;
int i;
long duration;
do {
i = nextI;
long start = System.nanoTime();
run(i);
duration = System.nanoTime() - start;
nextI = (i << 1) | 1;
} while (duration < 100000000 && nextI > 0);
return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return name + "\t" + time() + " ns";
}
static class C {
public Integer foo() {
return 1;
}
}
static final MethodHandle sfmh;
static {
try {
Method m = C.class.getMethod("foo");
sfmh = MethodHandles.lookup().unreflect(m);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
final C invocationTarget = new C();
final Method m = C.class.getMethod("foo");
final Method am = C.class.getMethod("foo");
am.setAccessible(true);
final MethodHandle mh = sfmh;
Bench[] marks = {
new Bench("reflective invocation (without setAccessible)") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += (Integer) m.invoke(invocationTarget);
}
return x;
}
},
new Bench("reflective invocation (with setAccessible)") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += (Integer) am.invoke(invocationTarget);
}
return x;
}
},
new Bench("methodhandle invocation") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += (Integer) mh.invokeExact(invocationTarget);
}
return x;
}
},
new Bench("static final methodhandle invocation") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += (Integer) sfmh.invokeExact(invocationTarget);
}
return x;
}
},
new Bench("direct invocation") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += invocationTarget.foo();
}
return x;
}
},
};
for (Bench bm : marks) {
System.out.println(bm);
}
}
}
我与有些过时笔记本
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode, sharing)
这种打印:
reflective invocation (without setAccessible) 568.506 ns
reflective invocation (with setAccessible) 42.377 ns
methodhandle invocation 27.461 ns
static final methodhandle invocation 9.402 ns
direct invocation 9.363 ns
更新:由于Irreputable指出,服务器虚拟机具有略微不同的性能特点,所以在使用的服务器MethodHandle VM只会帮助,如果你可以把它放在一个静态的决赛场 ,在这种情况下,虚拟机可以内嵌调用:
reflective invocation (without setAccessible) 9.736 ns
reflective invocation (with setAccessible) 7.113 ns
methodhandle invocation 26.319 ns
static final methodhandle invocation 0.045 ns
direct invocation 0.044 ns
我建议你衡量你的特定用例。
调用barReadMethod.setAccessible(true);
关闭安全检查,可以让它更快一点。 即使是可访问的,它有,否则检查。
如果我运行使用getter方法有和没有访问真实的。
class Main {
static class A {
private final Integer i;
A(Integer i) {
this.i = i;
}
public Integer getI() {
return i;
}
}
public static void main(String... args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
A[] as = new A[100000];
for (int i = 0; i < as.length; i++)
as[i] = new A(i);
for (int i = 0; i < 5; i++) {
long time1 = timeSetAccessible(as);
long time2 = timeNotSetAccessible(as);
System.out.printf("With setAccessible true %.1f ns, Without setAccessible %.1f ns%n",
(double) time1 / as.length, (double) time2 / as.length);
}
}
static long dontOptimiseAvay = 0;
private static long timeSetAccessible(A[] as) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Method getter = A.class.getDeclaredMethod("getI");
getter.setAccessible(true);
dontOptimiseAvay = 0;
long start = System.nanoTime();
for (A a : as) {
dontOptimiseAvay += (Integer) getter.invoke(a);
}
return System.nanoTime() - start;
}
private static long timeNotSetAccessible(A[] as) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Method getter = A.class.getDeclaredMethod("getI");
// getter.setAccessible(true);
dontOptimiseAvay = 0;
long start = System.nanoTime();
for (A a : as) {
dontOptimiseAvay += (Integer) getter.invoke(a);
}
return System.nanoTime() - start;
}
}
版画
With setAccessible true 106.4 ns, Without setAccessible 126.9 ns
With setAccessible true 5.4 ns, Without setAccessible 29.4 ns
With setAccessible true 3.2 ns, Without setAccessible 9.9 ns
With setAccessible true 3.1 ns, Without setAccessible 9.0 ns
With setAccessible true 3.1 ns, Without setAccessible 8.9 ns
对于一个简单的getter,使用setAccessible(true)可以快三倍。
如果上面讨论的静态最终MethodHandle选项是不实际的/可能的,另一个选择是将动态地生成使用类bytebuddy其具有一个单一的方法服用FOO,调用于foo杆方法并返回结果。
这将提供关于酒吧的个别调用基本上是一样直接调用(包装的通话将可能最终会在内衬)的性能。
然而,这将招致生成的类和方法的字节码的1和时间成本。 这样做的成本根据bytebuddy网站是在为200ns附近。
文章来源: Calling a getter in Java though reflection: What's the fastest way to repeatedly call it (performance and scalability wise)?