alo*_*loo 38 java reflection performance field methodhandle
我正在写一些调用的代码Field.set和Field.get数千次.显然,由于反射,这是非常缓慢的.
我想看看我是否可以MethodHandle在Java7中提高性能.到目前为止,这就是我所拥有的:
而不是field.set(pojo, value),我正在做:
private static final Map<Field, MethodHandle> setHandles = new HashMap<>();
MethodHandle mh = setHandles.get(field);
if (mh == null) {
mh = lookup.unreflectSetter(field);
setHandles.put(field, mh);
}
mh.invoke(pojo, value);
Run Code Online (Sandbox Code Playgroud)
但是,这似乎没有比使用反射的Field.set调用更好.我在这里做错了吗?
我读到使用invokeExact可能会更快,但当我尝试使用它时,我得到了一个java.lang.invoke.WrongMethodTypeException
有没有人成功地优化了对Field.set或Field.get的重复调用?
Ale*_*lev 62
2015-06-01:更新以反映@ JoeC关于句柄是静态的另一个案例的评论.还更新到最新的JMH并重新使用现代硬件.结论几乎保持不变.
请做适当的基准测试,可以说与JMH并不那么难.一旦你这样做,答案就变得很明显了.它还可以展示正确使用invokeExact(需要目标/源1.7来编译和运行):
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MHOpto {
private int value = 42;
private static final Field static_reflective;
private static final MethodHandle static_unreflect;
private static final MethodHandle static_mh;
private static Field reflective;
private static MethodHandle unreflect;
private static MethodHandle mh;
// We would normally use @Setup, but we need to initialize "static final" fields here...
static {
try {
reflective = MHOpto.class.getDeclaredField("value");
unreflect = MethodHandles.lookup().unreflectGetter(reflective);
mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
static_reflective = reflective;
static_unreflect = unreflect;
static_mh = mh;
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}
@Benchmark
public int plain() {
return value;
}
@Benchmark
public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) reflective.get(this);
}
@Benchmark
public int dynamic_unreflect_invoke() throws Throwable {
return (int) unreflect.invoke(this);
}
@Benchmark
public int dynamic_unreflect_invokeExact() throws Throwable {
return (int) unreflect.invokeExact(this);
}
@Benchmark
public int dynamic_mh_invoke() throws Throwable {
return (int) mh.invoke(this);
}
@Benchmark
public int dynamic_mh_invokeExact() throws Throwable {
return (int) mh.invokeExact(this);
}
@Benchmark
public int static_reflect() throws InvocationTargetException, IllegalAccessException {
return (int) static_reflective.get(this);
}
@Benchmark
public int static_unreflect_invoke() throws Throwable {
return (int) static_unreflect.invoke(this);
}
@Benchmark
public int static_unreflect_invokeExact() throws Throwable {
return (int) static_unreflect.invokeExact(this);
}
@Benchmark
public int static_mh_invoke() throws Throwable {
return (int) static_mh.invoke(this);
}
@Benchmark
public int static_mh_invokeExact() throws Throwable {
return (int) static_mh.invokeExact(this);
}
}
Run Code Online (Sandbox Code Playgroud)
在1x4x2 i7-4790K,JDK 8u40,Linux x86_64上它产生:
Benchmark Mode Cnt Score Error Units
MHOpto.dynamic_mh_invoke avgt 25 4.393 ± 0.003 ns/op
MHOpto.dynamic_mh_invokeExact avgt 25 4.394 ± 0.007 ns/op
MHOpto.dynamic_reflect avgt 25 5.230 ± 0.020 ns/op
MHOpto.dynamic_unreflect_invoke avgt 25 4.404 ± 0.023 ns/op
MHOpto.dynamic_unreflect_invokeExact avgt 25 4.397 ± 0.014 ns/op
MHOpto.plain avgt 25 1.858 ± 0.002 ns/op
MHOpto.static_mh_invoke avgt 25 1.862 ± 0.015 ns/op
MHOpto.static_mh_invokeExact avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_reflect avgt 25 4.274 ± 0.011 ns/op
MHOpto.static_unreflect_invoke avgt 25 1.859 ± 0.002 ns/op
MHOpto.static_unreflect_invokeExact avgt 25 1.858 ± 0.002 ns/op
Run Code Online (Sandbox Code Playgroud)
...这表明MH在这种特殊情况下实际上比Reflection快得多(这是因为对私有字段的访问检查是在查找时完成的,而不是在调用时完成的).dynamic_*案例模拟了MethodHandles和/或Fields不是静态知道的情况,例如拉出Map<String, MethodHandle>或类似的情况.相反,static_*案例是那些静态知道的工具.
请注意,反射性能与MethodHandles相同dynamic_*,这是因为在JDK 8中反射被进一步优化(因为实际上,您不需要访问检查来读取您自己的字段),因此答案可能是"只是"切换到JDK 8;)
static_*案件甚至更快,因为MethoHandles.invoke呼叫是积极内联的.这消除了MH情况下的部分类型检查.但是,在反思案例中,仍然存在快速检查,因此,它落后了.
Hol*_*ger 16
更新:由于有些人开始就"如何进行基准测试"进行毫无意义的讨论,我会强调我的答案中包含的问题的解决方案,现在就在开头:
invokeExact通过将MethodHandle使用转换为作为参数asType的句柄,您甚至可以在您没有确切类型签名的反射上下文中使用Object.在受到invoke和之间性能差异影响的环境中invokeExact,invokeExact在这样的转换句柄上使用仍然比invoke在直接方法句柄上使用更快.
原始答案:
问题确实是你没有使用invokeExact.下面是一个小的基准程序,显示了增加int字段的不同方法的结果.使用invoke而不是invokeExact导致性能下降到反射速度以下.
你收到了WrongMethodTypeException因为MethodHandle强类型.它期望精确的调用签名匹配字段和所有者的类型类型.但是您可以使用句柄创建一个新的MethodHandle包装必要的类型转换.使用invokeExact通用签名(即(Object,Object)Object)在该句柄上使用将比使用invoke动态类型转换更有效.
我的机器使用1.7.0_40的结果是:
direct : 27,415ns reflection : 1088,462ns method handle : 7133,221ns mh invokeExact: 60,928ns generic mh : 68,025ns
并使用-serverJVM产生令人困惑的结果
direct : 26,953ns reflection : 629,161ns method handle : 1513,226ns mh invokeExact: 22,325ns generic mh : 43,608ns
我不认为它看起来MethodHandle比直接操作更快,但它证明MethodHandle在Java7上的速度并不慢.
而泛型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;
// check (use) the value to avoid over-optimizations
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑感谢holger我注意到我真的应该使用invokeExact,所以我决定删除关于其他jdks的东西并只使用invokeExact......使用-server与否仍然对我没有真正的影响
使用反射和使用 MethodHandles 之间的主要区别在于,对于反射,您对每个调用进行安全检查,在 MethodHandles 的情况下,仅用于创建句柄。
如果你看这个
class Test {
public Object someField;
public static void main(String[] args) throws Exception {
Test t = new Test();
Field field = Test.class.getDeclaredField("someField");
Object value = new Object();
for (int outer=0; outer<50; outer++) {
long start = System.nanoTime();
for (int i=0; i<100000000; i++) {
field.set(t, value);
}
long time = (System.nanoTime()-start)/1000000;
System.out.println("it took "+time+"ms");
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后我在 jdk7u40 上使用我的计算机时间为 45000 毫秒(尽管 jdk8 和 pre 7u25 性能要好得多)
现在让我们看一下使用句柄的同一个程序
class Test {
public Object someField;
public static void main(String[] args) throws Throwable {
Test t = new Test();
Field field = Test.class.getDeclaredField("someField");
MethodHandle mh = MethodHandles.lookup().unreflectSetter(field);
Object value = new Object();
for (int outer=0; outer<50; outer++) {
long start = System.nanoTime();
for (int i=0; i<100000000; i++) {
mh.invokeExact(t, value);
}
long time = (System.nanoTime()-start)/1000000;
System.out.println("it took "+time+"ms");
}
}
}
Run Code Online (Sandbox Code Playgroud)
7u40 表示大约 1288 毫秒。所以我可以在 7u40 上确认 Holger 的 30 次。在 7u06 上,此代码处理会更慢,因为反射要快几倍,而在 jdk8 上,一切又都是新的。
至于为什么你没有看到改善......很难说。我所做的是微基准测试。这根本无法说明实际应用程序的任何情况。但是使用这些结果,我会假设您要么使用旧的 jdk 版本,要么不经常重复使用句柄。因为虽然执行句柄可以更快,但句柄的创建可能比字段的创建花费更多。
现在最大的问题点......我确实看到你想要谷歌应用引擎......而且我必须说,你可以根据需要在本地进行测试,最终重要的是应用程序在谷歌上的性能网站将。Afaik 他们使用修改后的 OpenJDK,但是他们没有说什么版本有什么修改。由于 Jdk7 如此不稳定,您可能不走运。也许他们为反射添加了特殊的代码,然后所有的赌注都被取消了。甚至忽略这一点......也许支付模式再次发生变化,但通常您希望通过缓存来避免数据存储访问,因为它会产生成本。如果这仍然成立,那么任何句柄将被称为平均 10.000 次是否现实?
在JDK 7和8中,MethodHandles 有一个问题22(我尚未测试JDK 9或更高版本):如果MethodHandle在静态字段中,则它速度很快(与直接访问一样快)。否则,它们会像反射一样缓慢。如果您的框架反映了n个getter或setter,在编译时不知道n,则MethodHandles对您可能没有用。
我写了一篇文章,对所有不同的方法进行了基准测试,以加快反射速度。
使用LambdaMetafactory(或其他更奇怪的方法,例如代码生成)来加快调用getter和setter的速度。这是获取方法的要点(对于setter,请使用BiConsumer):
public final class MyAccessor {
private final Function getterFunction;
public MyAccessor() {
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(lookup,
"apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class)),
MethodType.methodType(String.class, Person.class));
getterFunction = (Function) site.getTarget().invokeExact();
}
public Object executeGetter(Object bean) {
return getterFunction.apply(bean);
}
}
Run Code Online (Sandbox Code Playgroud)