如何提高 Field.set 的性能(可能使用 MethodHandles)?

我正在编写一些调用Field.set的代码,并且调用了数千次。显然,由于反射,这是非常缓慢的。Field.get

我想看看我是否可以在Java 7中使用MethodHandle来提高性能。到目前为止,这是我所拥有的:

而不是,我正在做: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);

但是,这似乎并不比使用反射的 Field.set 调用性能更好。我在这里做错了什么吗?

我读到使用 invokeExact 可以更快,但是当我尝试使用它时,我得到了一个 java.lang.invoke.ErrorMethodTypeException

有没有人能够成功地优化对Field.set或Field.get的重复调用?


答案 1

2015-06-01:进行了更新,以反映@JoeC对句柄为静态时的另一种情况的评论。还更新到最新的JMH并在现代硬件上重新运行。结论几乎保持不变。

请做适当的基准测试,可以说JMH并不难。一旦你这样做了,答案就变得显而易见了。它还可以展示正确使用(需要目标/源代码1.7来编译和运行):invokeExact

@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);
    }

}

在 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

...这表明在这种特殊情况下,MH确实比Reflempive快得多(这是因为针对私有字段的访问检查是在查找时完成的,而不是在调用时完成的)。 案例模拟和/或不静态已知的情况,例如从中拉出或类似的东西。相反,是调用程序静态已知的情况。dynamic_*MethodHandlesFieldsMap<String, MethodHandle>static_*

请注意,在某些情况下,反射性能与MethodHandles相当,这是因为反射在JDK 8中得到了进一步的大幅优化(因为实际上,您不需要访问检查来读取自己的字段),所以答案可能是“只是”切换到JDK 8;)dynamic_*

static_*案例甚至更快,因为调用是主动内联的。这消除了MH情况下的部分类型检查。但是,在反射情况下,仍然存在快速检查,因此,它滞后。MethoHandles.invoke


答案 2

更新:由于有些人开始了关于“如何进行基准测试”的毫无意义的讨论,我将强调我的答案中包含的问题的解决方案,现在就在开头:

即使在没有确切类型签名的反射上下文中,也可以通过将 using asType 转换为将 asType 转换为取参数的句柄来使用。在受 和 之间的性能差异影响的环境中,在这样的转换句柄上使用仍然比在直接方法句柄上使用快得多。invokeExactMethodHandleObjectinvokeinvokeExactinvokeExactinvoke


原答:

问题确实是您没有使用。下面是一个小的基准程序,显示了不同方式递增字段的结果。而不是使用会导致性能下降到反射速度以下。invokeExactintinvokeinvokeExact

您会收到,因为 是强类型化的。它需要与字段和所有者的类型类型匹配的精确调用签名。但是,您可以使用该句柄创建一个新的包装必要的类型转换。在该句柄上使用泛型签名(即 )仍然比使用动态类型转换更有效。WrongMethodTypeExceptionMethodHandleMethodHandleinvokeExact(Object,Object)Objectinvoke

在我的计算机上使用 1.7.0_40 的结果是:

direct        :   27,415ns
reflection    : 1088,462ns
method handle : 7133,221ns
mh invokeExact:   60,928ns
generic mh    :   68,025ns

并使用JVM屈服于令人困惑-server

direct        :   26,953ns
reflection    :  629,161ns
method handle : 1513,226ns
mh invokeExact:   22,325ns
generic mh    :   43,608ns

我不认为它比直接操作更快在现实生活中有太大的相关性,但它证明了Java7上的s并不慢。MethodHandleMethodHandle

泛型仍然会优于反射(而使用则不会)。MethodHandleinvoke

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);
    }
  }
}

推荐