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

Java 如何提高Field.set的性能(可能使用MethodHandles)?,java,performance,reflection,field,methodhandle,Java,Performance,Reflection,Field,Methodhandle,我正在编写一些调用和字段的代码。获取数千次。显然,这是非常缓慢的,因为 我想看看是否可以在Java7中提高性能。到目前为止,我的情况如下: 我现在做的不是field.set(pojo,value),而是: private static final Map<Field, MethodHandle> setHandles = new HashMap<>(); MethodHandle mh = setHandles.get(field); if (mh == null) {

我正在编写一些调用和
字段的代码。获取
数千次。显然,这是非常缓慢的,因为

我想看看是否可以在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);
private static final Map setHandles=new HashMap();
MethodHandle mh=setHandles.get(字段);
如果(mh==null){
mh=查找。未反射设置器(字段);
setHandles.put(字段,mh);
}
调用(pojo,值);
但是,这似乎并不比使用反射的Field.set调用更好。我做错什么了吗

我读到使用可能会更快,但当我尝试使用时,我得到了一个答案


是否有人成功地优化了对Field.set或Field.get的重复调用?

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

通过将使用的
MethodHandle
转换为以
Object
为参数的句柄,您甚至可以在没有确切类型签名的反射上下文中使用
invokeExact
。在受
invoke
invokeExact
之间性能差异影响的环境中,在这种转换句柄上使用
invokeExact
仍然比在直接方法句柄上使用
invoke
快得多


原始答复:

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

您将收到
ErrorMethodTypeException
,因为
MethodHandle
是强类型的。它需要一个完全匹配字段类型和所有者的调用签名。但是您可以使用句柄创建一个新的
MethodHandle
来包装必要的类型转换。使用泛型签名(即
(对象,对象)对象
)在该句柄上使用
invokeExact
,仍然比使用带有动态类型转换的
invoke
更有效

在我使用1.7.0(40)的机器上的结果是:

direct : 27,415ns reflection : 1088,462ns method handle : 7133,221ns mh invokeExact: 60,928ns generic mh : 68,025ns 我不认为看到
MethodHandle
比直接操作快与现实生活有多大关系,但它证明了
MethodHandle
s在Java7上并不慢

通用的
MethodHandle
仍将优于反射(而使用
invoke
则不然)

import java.lang.invoke.MethodHandle;
导入java.lang.invoke.MethodHandles;
导入java.lang.reflect.Field;
公共类FieldMethodHandle
{
公共静态void main(字符串[]args)
{
最终整数预热=1_000_000,迭代次数=1_000_000;

对于(inti=0;i编辑多亏了holger,我注意到我真的应该使用invokeExact,所以我决定删除关于其他JDK的内容,只使用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");
        }
    }
}
类测试{
公共对象字段;
公共静态void main(字符串[]args)引发异常{
测试t=新测试();
Field=Test.class.getDeclaredField(“someField”);
对象值=新对象();

对于(int-outer=0;outer2015-06-01:更新以反映@JoeC关于另一个句柄为静态的情况的评论。还更新为最新的JMH并在现代硬件上重新运行。结论几乎不变。

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

在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确实比反射快得多(这是因为对私有字段的访问检查是在查找时进行的,而不是在调用时).
dynamic.*
案例模拟当
MethodHandles
和/或
字段
不是静态已知时的情况,例如从
Map
或类似的东西中提取。相反,
static.*
案例是调用程序静态已知的情况

<注意>反射性能与<>代码> DealICy**/Cudio>情况下的方法处理一致,这是因为反射在JDK 8中被进一步优化了(因为实际上,你不需要访问检查来读取你自己的字段),所以答案可能是“仅仅”切换到JDK 8;)< /P>
static.*
情况更快,因为
MethodHandles.invoke
调用是积极的内联调用。这消除了MH情况下的部分类型检查。但是,在反射情况下,仍然存在快速检查,因此它会落后。

JDK 7和8中的MethodHandles有一个陷阱22(我还没有测试JDK 9或更高版本):如果MethodHandle位于静态字段中,它会很快(与直接访问一样快)。否则它们会像反射一样慢。如果您的框架反射了n个getter或setter,其中n在编译时是未知的,那么MethodHandles可能对您没有用处

我写道

使用LambdaMetafactory(或更奇特的方法,如代码生成)来加速调用getter和setter。下面是getter的要点(对于setter,使用
BiConsumer
):

信息技术
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");
        }
    }
}
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");
        }
    }
}
@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);
    }

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

}