Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a getter in Java though reflection: What's the fastest way to repeatedly call it (performance and scalability wise)?

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.

Suppose I have:

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

And I need to do something like 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?

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

like image 507
Geoffrey De Smet Avatar asked Jan 03 '13 20:01

Geoffrey De Smet


People also ask

Is reflection faster Java?

Adding setAccessible(true) call makes these reflection calls faster, but even then it takes 5.5 nanoseconds per call. Reflection is 104% slower than direct access (so about twice as slow). It also takes longer to warm up.

Why is Java reflection so slow?

Reflection is slow for a few obvious reasons: The compiler can do no optimization whatsoever as it can have no real idea about what you are doing. This probably goes for the JIT as well. Everything being invoked/created has to be discovered (i.e. classes looked up by name, methods looked at for matches etc)


1 Answers

You might use a MethodHandle. Its Javadoc writes:

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.

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)

this prints:

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

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.

like image 113
meriton Avatar answered Sep 18 '22 13:09

meriton