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

查看:22
本文介绍了通过反射在 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 中的工厂方法,由核心反射 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 + "	" + 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 所指出的,服务器 VM 具有一些不同的性能特征,因此在服务器 VM 中使用 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天全站免登陆