19

In my application obj.getClass().isArray() is called very frequently and become the bottleneck of the app.
I want to check efficiently at run-time if an object is an array. Primitive array and object array should return true.
The way I can imagine is to instanceof all primtive arrays, but cannot handle types like int[][]. And the app is used as lib, so I cannot list all types.
Is there any clue for that?

Charu Khurana
  • 4,511
  • 8
  • 47
  • 81
Nickolas
  • 780
  • 8
  • 27
  • 2
    This does not answer your question, but it's a nice addition to the discussion: http://stackoverflow.com/questions/219881/java-array-reflection-isarray-vs-instanceof – Petr Janeček Apr 23 '13 at 13:27
  • 10
    I think the question is why you have to call that method frequently? I'd hope that if there were a faster way, the JDK developers would base their implementation on it. If there isn't, you have to give more detail about the actual problem you are trying to solve. – Axel Apr 23 '13 at 13:30
  • 3
    Your `instanceof` reasoning is not entirely correct. `int[][] instanceof Object[]` yields `true` (just as `int[] instanceof Object`), so those cases are covered... Not sure if this is relevant to your actual problem domain, though (see Axel's comment). – Lukas Eder Apr 23 '13 at 13:33
  • 2
    What evidence have you that this method is slow? And how slow is it - what's the average nanosecond duration? – Bohemian Apr 23 '13 at 13:38
  • @Bohemian By JProfile sampling, this function takes more than 10% persent of CPU for all variables security checking. And I profile isArray() myself, it is mush slower than intanceof. – Nickolas Apr 23 '13 at 13:59
  • Please, back your claims, as [I cannot confirm](http://stackoverflow.com/a/16171137/521799) that `instanceof` checks are really much faster than `isArray()`. Note, profilers do not give accurate results here, as instrumentation introduces a significant measuring overhead! – Lukas Eder Apr 23 '13 at 14:04
  • Class.isArray() is a native method, and should be quite fast -- mostly just the call overhead. Also, being a native method, it would be hard to profile. `instanceof` might be a hair faster, or not -- it's more complex logic but probably less call overhead. – Hot Licks Apr 23 '13 at 15:04

3 Answers3

8

isArray() is the most efficient way to check if an object is an instance of an array at runtime. If the performance is a problem, you can use one of the following methods to address it:

  • Refactor your code so array objects and non-array objects are handled separately, so the results of isArray() are known at compile time.
  • Use local variables and/or arguments to cache the value of isArray() during an operation, so it only needs to be called once.
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
  • 2
    I agree with refactoring, but can anyone please back their claims that `isArray()` is so much slower than `instanceof`? I feel that caching the result might do more harm than solve problems here! – Lukas Eder Apr 23 '13 at 14:03
  • It seems your (unsubstantiated) claim that `usArray()` is fastest is wrong - see sp00n's answer. I do agree with suggestions though. – Bohemian Apr 23 '13 at 14:24
  • @LukasEder I've just edited, results seem to behave the same way. – sp00m Apr 23 '13 at 14:51
8

A benchmark I've just done gave the following results:

{s instanceof Object[]} spends 44ms
{s.getClass().getName().charAt(0) == '['} spends 58ms
{s.getClass().isArray()} spends 303ms

Benchmark has been done using Benchmark.java, called with Main.java.


After having discussed the use of a final variable in the above benchmark, see the new results using a local one:

{s instanceof Object[]} spends 83ms
{s.getClass().getName().charAt(0) == '['} spends 93ms
{s.getClass().isArray()} spends 354ms

Even if the durations are all a bit longer (interesting btw), their order has been preserved.

Benchmark.java has been then called with this new Main.java.


And using a primitive array called with this other Main.java:

{a instanceof int[]} spends 71ms
{a.getClass().getName().charAt(0) == '['} spends 82ms
{a.getClass().isArray()} spends 340ms

Still the same results order.

sp00m
  • 47,968
  • 31
  • 142
  • 252
  • Can you post the benchmark code? I got the [opposite results](http://stackoverflow.com/a/16171137/521799) – Lukas Eder Apr 23 '13 at 13:58
  • @LukasEder [Benchmark.java](http://pastebin.com/pskJ1tSy) and [Main.java](http://pastebin.com/KbptdWaB). I've to say that it's a tool I developed on my own, so I hope there's no mistake in it. Do not hesitate to tell me if you noticed one though, maybe I did something wrong :) – sp00m Apr 23 '13 at 14:05
  • Yes, I suspect that `boolean b = s instanceof Object[];` is optimised away by the compiler, as `s` is `final` and the above expression thus always evaluates to true... – Lukas Eder Apr 23 '13 at 14:08
  • 3
    @sp00m Looking at the first character of the class name was a clever thing to try. I like it! – Nathaniel Waisbrot Apr 23 '13 at 14:11
  • 1
    @NathanielWaisbrot To be honest, this wasn't my idea but [@SebastienTardif's one](http://stackoverflow.com/a/1681141/1225328) :) – sp00m Apr 23 '13 at 14:20
  • @sp00m too bad I can give only +1 :) – linski Apr 23 '13 at 14:23
  • It won't matter much, but instead of using `System.currentTimeMillis()`, you should probably be using `System.nanoTime()`... – Lukas Eder Apr 23 '13 at 14:31
  • Hmm, the edit is biasing the test, as you're creating a new array at every iteration. Besides, I'm still not sure if the compiler / JVM wouldn't optimise things, as the `instanceof` check is quite obviously still always true. And then, primitive type arrays aren't considered... Oh well, benchmarking... :-) – Lukas Eder Apr 23 '13 at 14:58
  • 1
    Note that it's critical, in this benchmark, to have `s` be declared Object and be set in some other routine (preferably somewhat obfuscated). Eg, the other routine should have a `switch` setting the var with several different types, and pass in the `switch` variable from outside. – Hot Licks Apr 23 '13 at 15:07
  • @HotLicks & LukasEder: Right, seemingly, I still must have got few things to learn about benchmarking ;) Do you think you could provide a "Main.java" illustrating a relevant benchmark (for my personal knowledge) in your both opinions? In parallel, is my Benchmark.java unbiased enough? – sp00m Apr 23 '13 at 15:18
  • Your benchmark does not handle primitive arrays, which the OP clearly stated is a requirement. – Sam Harwell Apr 23 '13 at 15:31
  • One thing to keep in mind is that a JITC implementation might very likely cache the last class passed to `instanceof` at a given code location, and return the same result if it were passed again. At the very least, any test should alternate 2-3 different classes through the `instanceof`. – Hot Licks Apr 23 '13 at 16:32
2

From your comments, I conclude that you may be suffering from an interpretive mistake when investigating profiling results. Your profiler's method-level instrumentation might be heavily crippling getClass() and isArray() calls, while being unimpressed by instanceof expressions. In other words, you're probably measuring the measuring overhead of your profiler, here.

Besides, in a quick benchmark, I cannot back your claim. I've run the following, very silly test:

public class Test {
    public static void main(String[] args) {
        final int rep = 10000000;
        Object[] o = {
            null,
            1,
            "x",
            new Object[0],
            new Object[0][],
            new int[0],
            new int[0][]
        };

        // "Warmup" to avoid potential JVM startup overhead
        long x = 0;
        for (int i = 0; i < rep; i++) {
            x+=checkInstanceOf(o);
        }

        for (int i = 0; i < rep; i++) {
            x+=checkIsArray(o);
        }

        for (int i = 0; i < rep; i++) {
            x+=checkClassName(o);
        }

        // Actual test
        long t1 = System.nanoTime();
        for (int i = 0; i < rep; i++) {
            x+=checkInstanceOf(o);
        }

        long t2 = System.nanoTime();
        for (int i = 0; i < rep; i++) {
            x+=checkIsArray(o);
        }

        long t3 = System.nanoTime();
        for (int i = 0; i < rep; i++) {
            x+=checkClassName(o);
        }

        long t4 = System.nanoTime();

        System.out.println(t2 - t1);
        System.out.println(t3 - t2);
        System.out.println(t4 - t3);
    }

    private static int checkInstanceOf(Object[] o) {
        int i = 0;
        for (Object x : o) {
            if (x instanceof Object[]) i++;       // Perform some logic
            else if (x instanceof boolean[]) i++; // to keep the compiler or
            else if (x instanceof byte[]) i++;    // the JVM from optimising
            else if (x instanceof short[]) i++;   // this code away
            else if (x instanceof int[]) i++;
            else if (x instanceof long[]) i++;
            else if (x instanceof float[]) i++;
            else if (x instanceof double[]) i++;
            else if (x instanceof char[]) i++;
        }
        return i;
    }

    private static int checkIsArray(Object[] o) {
        int i = 0;
        for (Object x : o) {
            if (x != null && x.getClass().isArray()) i++;
        }
        return i;
    }

    private static int checkClassName(Object[] o) {
        int i = 0;
        for (Object x : o) {
            if (x != null && x.getClass().getName().charAt(0) == '[') i++;
        }
        return i;
    }
}

I'm getting:

394433000 // instanceof
110655000 // getClass().isArray()
396039000 // getClass().getName().charAt(0) == '['

So you cannot generally claim getClass().isArray() to be slower than a thorough set of instanceof checks. Of course, there is a lot of different ways to rewrite my test, but you get the idea.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • 1
    This benchmark is flawed. It does not take account of JVM warmup. (I wouldn't be surprised you would got opposite "results" by doing the tests in the other order ...) – Stephen C Apr 23 '13 at 14:08
  • @StephenC: Of course I had thought of that and tried both ways. I'll update with results... – Lukas Eder Apr 23 '13 at 14:13