给定一个类Foo和一个属性bar,我在编译时都不知道,我需要多次重复调用getter Foo.getBar().
假设我有:
Method barGetterMethod = ...; // Don't worry how I got this
Run Code Online (Sandbox Code Playgroud)
我需要做这样的事情:
for (Object foo : fooList) { // 1000000000 elements in fooList
Object bar = barGetterMethod.invoke(foo);
...
}
Run Code Online (Sandbox Code Playgroud)
与没有反射调用它相比,上面的实现仍然很慢.有更快的方法吗?
用Java反射调用getter的最快方法是什么?
mer*_*ike 28
您可以使用MethodHandle.它的Javadoc写道:
使用Lookup API中的工厂方法,可以将Core Reflection 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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
在我有点过时的笔记本上
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)
Run Code Online (Sandbox Code Playgroud)
这打印:
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
Run Code Online (Sandbox Code Playgroud)
更新:正如Irreputable指出的那样,服务器虚拟机的性能特征有所不同,因此在服务器虚拟机中使用MethodHandle只有在将其置于静态最终字段时才有用,在这种情况下,虚拟机可以内联调用:
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
Run Code Online (Sandbox Code Playgroud)
我建议您测量您的特定用例.
Pet*_*rey 10
调用barReadMethod.setAccessible(true);关闭安全检查,这可以使它更快一点.即使可以访问,也必须另行检查.
如果我运行使用带有和不带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;
}
}
Run Code Online (Sandbox Code Playgroud)
版画
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
Run Code Online (Sandbox Code Playgroud)
对于简单的getter,使用setAccessible(true)可以快三倍.