Search code examples
javareflectionbenchmarkingrtti

Java: instanceof vs class name switch performance


I was interested in performance question about determine type of object and write some benchmarks with 2 ways of determination.

Can someone explain me why variant with instanceof faster 350 times than variant with class name switching with strings?

Code:

class A {}
class B extends A {}

public class InstanceOfBenchmark {
    public static final Object a = new A();

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void testInstanceOf()
    {
        if (a instanceof B) {}
        else if (a instanceof String) {}
        else if (a instanceof ArrayList) {}
        else if (a instanceof HashMap) {}
        else if (a instanceof HashSet) {}
        else if (a instanceof A);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void testName() {
        String class_name = a.getClass().getSimpleName();

        switch (class_name) {
            case ("B") :
            case ("String") :
            case ("ArrayList") :
            case ("HashMap") :
            case ("HashSet") :
            case ("A") :
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(InstanceOfBenchmark.class.getSimpleName())
                .warmupIterations(20)
                .measurementIterations(100)
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

Results:

Benchmark                            Mode  Cnt     Score    Error   Units
InstanceOfBenchmark.testInstanceOf  thrpt  100  3482.001 ± 25.447  ops/us
InstanceOfBenchmark.testName        thrpt  100    10.579 ±  0.078  ops/us

I run tests with 200 times warmup and 2000 iteration too, result was same.


Solution

  • Bytecode for the String version:

       0: aload_0
       1: invokevirtual #2           // Method java/lang/Object.getClass:()Ljava/lang/Class;
       4: invokevirtual #3           // Method java/lang/Class.getSimpleName:()Ljava/lang/Str
       7: astore_1
       8: aload_1
       9: astore_2
      10: iconst_m1
      11: istore_3
      12: aload_2
      13: invokevirtual #4           // Method java/lang/String.hashCode:()I
      16: lookupswitch  { // 6
           -1932803762: 118
           -1932797868: 132
           -1808118735: 90
                    65: 146
                    66: 76
             578806391: 104
               default: 157
          }
      76: aload_2
      77: ldc           #5           // String B
      79: invokevirtual #6           // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      82: ifeq          157
      85: iconst_0
      86: istore_3
      87: goto          157
      90: aload_2
      91: ldc           #7          // String String
      93: invokevirtual #6          // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      96: ifeq          157
      99: iconst_1
     100: istore_3
     101: goto          157
     104: aload_2
     105: ldc           #8          // String ArrayList
     107: invokevirtual #6          // Method java/lang/String.equals:(Ljava/lang/Object;)Z
     110: ifeq          157
     113: iconst_2
     114: istore_3
     115: goto          157
     118: aload_2
     119: ldc           #9           // String HashMap
     121: invokevirtual #6           // Method java/lang/String.equals:(Ljava/lang/Object;)Z
     124: ifeq          157
     127: iconst_3
     128: istore_3
     129: goto          157
     132: aload_2
     133: ldc           #10           // String HashSet
     135: invokevirtual #6            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
     138: ifeq          157
     141: iconst_4
     142: istore_3
     143: goto          157
     146: aload_2
     147: ldc           #11           // String A
     149: invokevirtual #6            // Method java/lang/String.equals:(Ljava/lang/Object;)Z
     152: ifeq          157
     155: iconst_5
     156: istore_3
     157: iload_3
     158: tableswitch   { // 0 to 5
                     0: 196
                     1: 196
                     2: 196
                     3: 196
                     4: 196
                     5: 196
               default: 196
          }
     196: return
    

    Bytecode for the instanceof version:

      0: aload_0
      1: instanceof    #12                 // class B
      4: ifeq          10
      7: goto          57
     10: aload_0
     11: instanceof    #13                 // class java/lang/String
     14: ifeq          20
     17: goto          57
     20: aload_0
     21: instanceof    #14                 // class java/util/ArrayList
     24: ifeq          30
     27: goto          57
     30: aload_0
     31: instanceof    #15                 // class java/util/HashMap
     34: ifeq          40
     37: goto          57
     40: aload_0
     41: instanceof    #16                 // class java/util/HashSet
     44: ifeq          50
     47: goto          57
     50: aload_0
     51: instanceof    #17                 // class A
     54: ifeq          57
     57: return
    

    Does it still surprise you that instanceof is faster? The number of operations is significantly fewer.