55

I've always avoided Java reflection soley based on its reputation for slowness. I reached a point in the design of my current project where being able to use it would make my code much more readable and elegant, so I decided to give it a go.

I was simply astonished by the difference, I noticed at times almost 100x longer run times. Even in this simple example where it just instantiates an empty class, it's unbelievable.

class B {

}

public class Test {

    public static long timeDiff(long old) {
        return System.currentTimeMillis() - old;
    }

    public static void main(String args[]) throws Exception {

        long numTrials = (long) Math.pow(10, 7);

        long millis;

        millis = System.currentTimeMillis();

        for (int i=0; i<numTrials; i++) {
            new B();
        }
        System.out.println("Normal instaniation took: "
                 + timeDiff(millis) + "ms");

        millis = System.currentTimeMillis();

        Class<B> c = B.class;

        for (int i=0; i<numTrials; i++) {
            c.newInstance();
        }

        System.out.println("Reflecting instantiation took:" 
              + timeDiff(millis) + "ms");

    }
}

So really, my questions are

  • Why is it this slow? Is there something I'm doing wrong? (even the example above demonstrates the difference). I have a hard time believing that it can really be 100x slower than normal instantiation.

  • Is there something else that can be better used for treating code as Data (bear in mind I'm stuck with Java for now)

Jonas
  • 121,568
  • 97
  • 310
  • 388
Mike
  • 58,961
  • 76
  • 175
  • 221
  • 1
    There is also a thread at http://stackoverflow.com/questions/435553/java-reflection-performance that urges you to read the documentation of the reflection API – Aleksi Yrttiaho Sep 08 '09 at 06:56
  • 2
    Benchmarking the JVM is difficult. The Hotspot JVM will not be fooled by a naive test like this one that actually does nothing. – skaffman Sep 08 '09 at 07:34
  • Just a note that that is a really bad way to do benchmarks, even for a microbenchmark. – Tom Hawtin - tackline Sep 08 '09 at 08:10
  • 1
    Benchmark is not taking into consideration JVM warmup / compilation phase. Wrap main's content in a loop, and you'll see completely different numbers on subsequent runs. – Mike Nov 06 '12 at 17:13
  • 1
    I just witnessed the JVM optimizing reflection 35 fold. Running the test repeatedly in a loop is how you test optimized code. First iteration: 3045ms, second iteration: 2941ms, third iteration: 90ms, fourth iteration: 83ms. Code: c.newInstance(i). c is a Constructor. Non reflective code: new A(i), which yields 13, 4, 3.. ms times. So yes, reflection in this case was slow, but not nearly as much slower as what people are concluding, because every test I'm seeing, they're simply running the test once without giving the JVM the opportunity to replace byte codes with machine code. – Mike Nov 06 '12 at 17:42

6 Answers6

51

Reflection is slow for a few obvious reasons:

  1. 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
  2. Everything being invoked/created has to be discovered (i.e. classes looked up by name, methods looked at for matches etc)
  3. Arguments need to be dressed up via boxing/unboxing, packing into arrays, Exceptions wrapped in InvocationTargetExceptions and re-thrown etc.
  4. All the processing that Jon Skeet mentions here.

Just because something is 100x slower does not mean it is too slow for you assuming that reflection is the "right way" for you to design your program. For example, I imagine that IDEs make heavy use of reflection and my IDE is mostly OK from a performance perspective.

After all, the overhead of reflection is likely to pale into insignificance when compared with, say, parsing XML or accessing a database!

Another point to remember is that micro-benchmarks are a notoriously flawed mechanism for determining how fast something is in practice. As well as Tim Bender's remarks, the JVM takes time to "warm up", the JIT can re-optimize code hotspots on-the-fly etc.

Community
  • 1
  • 1
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 11
    It cannot be over emphasized that using reflection is only slow compared to using the equivalent non-reflection code. Reading or writing to the HD or parsing xml or using the network or accessing a database (which is accessing HD and Network) are all slower than reflection. – Stefan Rusek Sep 08 '09 at 07:39
  • Reflection might access the HDD as well – Sebastian Oct 01 '12 at 21:22
  • 3
    I just witnessed the JVM optimizing reflection 35 fold. Running the test repeatedly in a loop is how you test optimized code. First iteration: 3045ms, second iteration: 2941ms, third iteration: 90ms, fourth iteration: 83ms. Code: c.newInstance(i). c is a Constructor. Non reflective code: new A(i), which yields 13, 4, 3.. ms times. So yes, reflection in this case was slow, but not nearly as much slower as what people are concluding, because every test I'm seeing, they're simply running the test once without giving the JVM the opportunity to replace byte codes with machine code. – Mike Nov 06 '12 at 17:41
41

Your test may be flawed. Generally though the JVM may optimize the normal instantiation but could not make optimizations for the reflective use case.

For those wondering what the times were, I added a warmup phase and used an array to maintain the created objects (more similar to what a real program might do). I ran the test code on my OSX, jdk7 system and got this:

Reflecting instantiation took:5180ms
Normal instaniation took: 2001ms

Modified test:

public class Test {

    static class B {

    }

    public static long timeDiff(long old) {
        return System.nanoTime() - old;
    }

    public static void main(String args[]) throws Exception {

        int numTrials = 10000000;
        B[] bees = new B[numTrials];
        Class<B> c = B.class;
        for (int i = 0; i < numTrials; i++) {
            bees[i] = c.newInstance();
        }
        for (int i = 0; i < numTrials; i++) {
            bees[i] = new B();
        }

        long nanos;

        nanos = System.nanoTime();
        for (int i = 0; i < numTrials; i++) {
            bees[i] = c.newInstance();
        }
        System.out.println("Reflecting instantiation took:" + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");

        nanos = System.nanoTime();
        for (int i = 0; i < numTrials; i++) {
            bees[i] = new B();
        }
        System.out.println("Normal instaniation took: " + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");
    }


}
Tim Bender
  • 20,112
  • 2
  • 49
  • 58
  • 1
    Do you have an idea, why normal instantiation time varies so heavily? – Vanya Sep 08 '09 at 09:42
  • 3
    Nope. I noticed the discrepancy and wondered the same thing, but chose not to investigate. BTW, I discovered this is a repeat of: http://stackoverflow.com/questions/435553/java-reflection-performance ...and I support both the main answer and Marcus Downing's contribution as the second answer. – Tim Bender Sep 09 '09 at 07:14
  • I do hope the JVM doesn't do this optimization if the constructor of B has a side effect? – Bart van Heukelom Jun 26 '10 at 15:11
  • 3
    Benchmark is not taking into consideration JVM warmup / compilation phase. Wrap main's content in a loop, and you'll see completely different numbers on subsequent runs. – Mike Nov 06 '12 at 17:14
  • @Mike Years later... I added that warmup phase you asked for. Btw, without maintaining the objects in the heap via an array, the times are considerably less (100ms and 300ms), so there is either a lot of heap maintenance overhead or definitely some (stack?) optimization occurs when the objects are not scoped beyond the block in which they are created. – Tim Bender Jun 03 '13 at 18:27
  • Tim, the JVM, at runtime, recreates optimized machine code compiled from the bytecodes, at runtime, in real time. A method has to be re-entered for the updated re-compiled method to be activated. So, the JVM has a ton of compilation stuff built in. But those compilation algorithms are bypassed on first pass so as to not incur the compilation time penalty to make for a faster initial response. – Mike Jul 17 '13 at 15:47
  • @Mike, no, that's not true. 2 things activate JIT compilations and while one of those things is functions (whereby this snippet does not trigger that JIT compilation), the other thing that triggers it is loops. From memory, I believe the default for C1 is 1500 and C2 is 3000 (?), so both loops far exceed that threshold. In other words, both loops (whether timed or not) should have went far past the threshold for the C2 to optimize each loop. – searchengine27 May 05 '22 at 17:12
  • @searchengine27 I don't think any content inside the method while the code of that method is running, at that moment, has any code replacement / optimization / jitting dropping replacing segments happening. Not during that time. The method will have to be re-entered for a jit'ed replacement to run. So you can loop till the cows come home, once a non jit'ed method is running, nothing is upgraded during that method run. – Mike May 07 '22 at 03:35
29

The JITted code for instantiating B is incredibly lightweight. Basically it needs to allocate enough memory (which is just incrementing a pointer unless a GC is required) and that's about it - there's no constructor code to call really; I don't know whether the JIT skips it or not but either way there's not a lot to do.

Compare that with everything that reflection has to do:

  • Check that there's a parameterless constructor
  • Check the accessibility of the parameterless constructor
  • Check that the caller has access to use reflection at all
  • Work out (at execution time) how much space needs to be allocated
  • Call into the constructor code (because it won't know beforehand that the constructor is empty)

... and probably other things I haven't even thought of.

Typically reflection isn't used in a performance-critical context; if you need dynamic behaviour like that, you could use something like BCEL instead.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
10

It seems that if you make the constructor accessible, it will execute much faster. Now it's only about 10-20 times slower than the other version.

    Constructor<B> c = B.class.getDeclaredConstructor();
    c.setAccessible(true);
    for (int i = 0; i < numTrials; i++) {
        c.newInstance();
    }

Normal instaniation took: 47ms
Reflecting instantiation took:718ms

And if you use the Server VM, it is able to optimize it more, so that it's only 3-4 times slower. This is quite typical performance. The article that Geo linked is a good read.

Normal instaniation took: 47ms
Reflecting instantiation took:140ms

But if you enable scalar replacement with -XX:+DoEscapeAnalysis then the JVM is able to optimize the regular instantiation away (it will be 0-15ms) but the reflective instantiation stays the same.

tur1ng
  • 1,082
  • 1
  • 10
  • 24
Esko Luontola
  • 73,184
  • 17
  • 117
  • 128
  • Now I'm curious, just because the blog is currently inaccessible to non-invited readers (yes, *WTF*). I don't even code in Java, but I got curious simply for the performance difference. – Camilo Martin Feb 05 '12 at 12:58
3
  • Reflection was very slow when first introduced, but has been sped up considerably in newer JREs
  • Still, it might not be a good idea to use reflection in an inner loop
  • Reflection-based code has low potential for JIT-based optimizations
  • Reflection is mostly used in connecting loosely-coupled components, i.e. in looking up concrete classes and methods, where only interfaces are known: dependeny-injection frameworks, instantiating JDBC implementation classes or XML parsers. These uses can often be done once at system startup, so the small inefficiency don't matter anyway!
mfx
  • 7,168
  • 26
  • 29
0

@Tim Bender 's code give these results on my machine(jdk_1.8_45, os_x 10.10, i7, 16G):

Reflecting instantiation took:1139ms Normal instaniation took: 4969ms

so it seems on modern JVM, the reflection code will be optimized also well.

bob
  • 1,107
  • 10
  • 16