更新:由于一些人开始了一个无意义的“如何进行基准测试”的讨论,我将强调我的答案中包含的解决方案,现在放在开头:
即使在反射上下文中使用
invokeExact
,您也可以通过使用
asType
将
MethodHandle
转换为以
Object
为参数的句柄来解决问题。在受到
invoke
和
invokeExact
之间性能差异影响的环境中,使用这样一个转换处理程序上的
invokeExact
仍然比在直接方法句柄上使用
invoke
快得多。
原始答案:
问题确实是您没有使用
invokeExact
。以下是一个小型基准测试程序,显示了不同方法递增
int
字段的结果。使用
invoke
而不是
invokeExact
会导致性能下降到低于反射的速度。
您收到
WrongMethodTypeException
,因为
MethodHandle
是强类型的。它期望一个精确的调用签名,匹配字段和所有者的类型。但是,您可以使用该句柄创建一个新的
MethodHandle
,其中包含必要的类型转换。在具有通用签名(即
(Object,Object)Object
)的处理程序上使用
invokeExact
仍将比使用动态类型转换的
invoke
更有效率。
我的机器上使用1.7.0_40的结果如下:
直接操作: 27,415ns
反射操作: 1088,462ns
方法句柄: 7133,221ns
mh invokeExact: 60,928ns
通用方法句柄: 68,025ns
并且使用
-server
JVM会产生令人困惑的结果:
直接操作: 26,953ns
反射操作: 629,161ns
方法句柄: 1513,226ns
mh invokeExact: 22,325ns
通用方法句柄: 43,608ns
我认为这没有太多实际意义,因为看到
MethodHandle
比直接操作更快,但它证明了在Java7上
MethodHandle
不慢。
而通用
MethodHandle
仍然优于反射(而使用
invoke
则不是)。
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
public class FieldMethodHandle
{
public static void main(String[] args)
{
final int warmup=1_000_000, iterations=1_000_000;
for(int i=0; i<warmup; i++)
{
incDirect();
incByReflection();
incByDirectHandle();
incByDirectHandleExact();
incByGeneric();
}
long direct=0, refl=0, handle=0, invokeExact=0, genericH=0;
for(int i=0; i<iterations; i++)
{
final long t0=System.nanoTime();
incDirect();
final long t1=System.nanoTime();
incByReflection();
final long t2=System.nanoTime();
incByDirectHandle();
final long t3=System.nanoTime();
incByDirectHandleExact();
final long t4=System.nanoTime();
incByGeneric();
final long t5=System.nanoTime();
direct+=t1-t0;
refl+=t2-t1;
handle+=t3-t2;
invokeExact+=t4-t3;
genericH+=t5-t4;
}
final int result = VALUE.value;
if(result != (warmup+iterations)*5) throw new AssertionError();
double r=1D/iterations;
System.out.printf("%-14s:\t%8.3fns%n", "direct", direct*r);
System.out.printf("%-14s:\t%8.3fns%n", "reflection", refl*r);
System.out.printf("%-14s:\t%8.3fns%n", "method handle", handle*r);
System.out.printf("%-14s:\t%8.3fns%n", "mh invokeExact", invokeExact*r);
System.out.printf("%-14s:\t%8.3fns%n", "generic mh", genericH*r);
}
static class MyValueHolder
{
int value;
}
static final MyValueHolder VALUE=new MyValueHolder();
static final MethodHandles.Lookup LOOKUP=MethodHandles.lookup();
static final MethodHandle DIRECT_GET_MH, DIRECT_SET_MH;
static final MethodHandle GENERIC_GET_MH, GENERIC_SET_MH;
static final Field REFLECTION;
static
{
try
{
REFLECTION = MyValueHolder.class.getDeclaredField("value");
DIRECT_GET_MH = LOOKUP.unreflectGetter(REFLECTION);
DIRECT_SET_MH = LOOKUP.unreflectSetter(REFLECTION);
GENERIC_GET_MH = DIRECT_GET_MH.asType(DIRECT_GET_MH.type().generic());
GENERIC_SET_MH = DIRECT_SET_MH.asType(DIRECT_SET_MH.type().generic());
}
catch(NoSuchFieldException | IllegalAccessException ex)
{
throw new ExceptionInInitializerError(ex);
}
}
static void incDirect()
{
VALUE.value++;
}
static void incByReflection()
{
try
{
REFLECTION.setInt(VALUE, REFLECTION.getInt(VALUE)+1);
}
catch(IllegalAccessException ex)
{
throw new AssertionError(ex);
}
}
static void incByDirectHandle()
{
try
{
Object target=VALUE;
Object o=GENERIC_GET_MH.invoke(target);
o=((Integer)o)+1;
DIRECT_SET_MH.invoke(target, o);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
static void incByDirectHandleExact()
{
try
{
DIRECT_SET_MH.invokeExact(VALUE, (int)DIRECT_GET_MH.invokeExact(VALUE)+1);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
static void incByGeneric()
{
try
{
Object target=VALUE;
Object o=GENERIC_GET_MH.invokeExact(target);
o=((Integer)o)+1;
o=GENERIC_SET_MH.invokeExact(target, o);
}
catch(Throwable ex)
{
throw new AssertionError(ex);
}
}
}
MethodHandle
可能会很慢。有一次我们也尝试用它们替换反射调用,结果它们实际上更糟糕了。希望在Java 8中情况会变得更好,因为MethodHandle
被用于创建lambda类。建议在JDK8上进行测试。 - ghik