通过反射调用Java中的getter:重复调用它的最快方法是什么(性能和可伸缩性方面)? [英] Calling a getter in Java though reflection: What's the fastest way to repeatedly call it (performance and scalability wise)?

查看:83
本文介绍了通过反射调用Java中的getter:重复调用它的最快方法是什么(性能和可伸缩性方面)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给定一个类 Foo 和一个属性 bar 我在编译时都不知道,我需要多次多次调用getter Foo.getBar()

Given a class Foo and a property bar, neither of which I know at compile time, I need to repeatedly call the getter Foo.getBar() many, many times.

假设我有:

Method barGetterMethod = ...; // Don't worry how I got this

我需要这样做:

for (Object foo : fooList) { // 1000000000 elements in fooList
    Object bar = barGetterMethod.invoke(foo);
    ...
}

上面的实施仍然非常缓慢称之为没有反思。有更快的方法吗?

The implementation above is still very slow compared to calling it without reflection. Is there a faster way?

用Java反射调用getter的最快方法是什么?

What's the fastest way of calling a getter with reflection in Java?

推荐答案

您可以使用 MethodHandle 。它的Javadoc写道:

You might use a MethodHandle. Its Javadoc writes:


在Lookup API中使用工厂方法,Core Reflection API对象表示的任何类成员都可以转换为行为等效方法句柄。例如,可以使用Lookup.unreflect将反射方法转换为方法句柄。生成的方法句柄通常提供对底层类成员的更直接和有效的访问。

Using factory methods in the Lookup API, any class member represented by a Core Reflection API object can be converted to a behaviorally equivalent method handle. For example, a reflective Method can be converted to a method handle using Lookup.unreflect. The resulting method handles generally provide more direct and efficient access to the underlying class members.

虽然这会减少开销,但是方法句柄仍会阻止JVM在调用时可能采用的某些优化(例如内联方法)使用通常的(非反射)字节码指令。这种优化是否有益取决于您如何使用该方法(如果该代码路径始终调用相同的方法,则内联可以提供帮助,如果每次都是不同的方法,可能不会)。

While this will reduce the overhead, method handles still prevent certain optimizations (such a method inlining) the JVM could employ if the call were made with the usual (non-reflective) byte code instructions. Whether such optimizations would be beneficial depends on how you use the method (if that code path always invokes the same method, inlining can help, if it is a different method each time, probably not).

以下微基准测试可能会让您大致了解反射,方法句柄和直接调用的相对性能:

The following microbenchmark might give you a rough idea about the relative performance of reflection, method handles, and direct invocation:

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

在我有点过时的笔记本上

on my somewhat dated notebook with

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)

打印:

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

更新:正如Irreputable指出的那样,服务器虚拟机的性能特征有所不同,因此在服务器虚拟机中使用MethodHandle的只有在你可以把它放在一个静态的最终字段时才会有帮助,其中VM可以内联呼叫:

Update: As Irreputable points out, the server VM has somewhat different performance characteristics, so using a MethodHandle in a server VM will only help if you can put it in a static final field, in which case the VM can inline the call:

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

我建议您测量您的特定用例。

I recommend that you measure your particular use case.

这篇关于通过反射调用Java中的getter:重复调用它的最快方法是什么(性能和可伸缩性方面)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆