The conditional statement is an important part of the program as well as the control means of the system business logic. The importance and frequency of use are second to none. How should we choose if or switch? How different are their performance? What’s the secret behind Switch performance? Let’s find out the answers to these questions.

switch VS if

As I mentioned in my previous post, 9 Tips to Make Your If Else Look More elegant, try to use switch because it has higher performance, but by how much? And why it’s high will be revealed in this article.

We still use the Java Microbenchmark Harness (JMH) framework provided by Oracle to test. First, we introduce the JMH framework and add the following configuration in the PEM. XML file:

<! -- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core --> <dependency> <groupId>org.openjdk.jmh</groupId> < artifactId > JMH - core < / artifactId > < version > 1.23 < / version > < / dependency >Copy the code

Then write the test code, we add 5 conditional judgment branches, the specific implementation code is as follows:

import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; @benchmarkMode (mode.averageTime) // Test completion time @outputTimeUnit (timeunit.nanoseconds) @Warmup(iterations = 2, time = 1, TimeUnit = timeunit.seconds) Iterations = 5 @Measurement(time = 1, timeUnit = timeunit.seconds) // Perform 5 iterations. @state (scope.thread) // One instance for each test Thread Public Class SwitchOptimizeTest {static Integer _NUM = 9. Public static void main(String[] args) throws RunnerException {// Start the benchmark. Options opt = new OptionsBuilder() Include (SwitchOptimizeTest. Class. GetSimpleName ()) / / to import the test class. The output ("/Users/admin/Desktop/jmh-switch.log") // Output test result.build (); new Runner(opt).run(); // Perform a test} @benchmark public voidswitchTest() {
        int num1;
        switch (_NUM) {
            case 1:
                num1 = 1;
                break;
            case 3:
                num1 = 3;
                break;
            case 5:
                num1 = 5;
                break;
            case 7:
                num1 = 7;
                break;
            case 9:
                num1 = 9;
                break;
            default:
                num1 = -1;
                break;
        }
    }

    @Benchmark
    public void ifTest() {
        int num1;
        if (_NUM == 1) {
            num1 = 1;
        } else if (_NUM == 3) {
            num1 = 3;
        } else if (_NUM == 5) {
            num1 = 5;
        } else if (_NUM == 7) {
            num1 = 7;
        } else if (_NUM == 9) {
            num1 = 9;
        } else{ num1 = -1; }}}Copy the code

The test results for the above code are as follows:

Note: The test environment for this article is JDK 1.8 / Mac Mini (2018)/Idea 2020.1

As can be seen from the above results (Score column), the average execution completion time of switch is about 2.33 times faster than that of IF.

Performance analysis

Why is switch performance so much better than if performance?

This starts with their bytecodes, which we generate using javac code as follows:

public class com.example.optimize.SwitchOptimize {
  static java.lang.Integer _NUM;

  public com.example.optimize.SwitchOptimize();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."
      
       ":()V
      
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #7 // Method switchTest:()V
       3: invokestatic  #12 // Method ifTest:()V
       6: return

  public static void switchTest();
    Code:
       0: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
       3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
       6: tableswitch   { // 1 to 9
                     1: 56
                     2: 83
                     3: 61
                     4: 83
                     5: 66
                     6: 83
                     7: 71
                     8: 83
                     9: 77
               default: 83
          }
      56: iconst_1
      57: istore_0
      58: goto          85
      61: iconst_3
      62: istore_0
      63: goto          85
      66: iconst_5
      67: istore_0
      68: goto          85
      71: bipush        7
      73: istore_0
      74: goto          85
      77: bipush        9
      79: istore_0
      80: goto          85
      83: iconst_m1
      84: istore_0
      85: return

  public static void ifTest();
    Code:
       0: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
       3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
       6: iconst_1
       7: if_icmpne     15
      10: iconst_1
      11: istore_0
      12: goto          81
      15: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
      18: invokevirtual #19 // Method java/lang/Integer.intValue:()I
      21: iconst_3
      22: if_icmpne     30
      25: iconst_3
      26: istore_0
      27: goto          81
      30: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
      33: invokevirtual #19 // Method java/lang/Integer.intValue:()I
      36: iconst_5
      37: if_icmpne     45
      40: iconst_5
      41: istore_0
      42: goto          81
      45: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
      48: invokevirtual #19 // Method java/lang/Integer.intValue:()I
      51: bipush        7
      53: if_icmpne     62
      56: bipush        7
      58: istore_0
      59: goto          81
      62: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
      65: invokevirtual #19 // Method java/lang/Integer.intValue:()I
      68: bipush        9
      70: if_icmpne     79
      73: bipush        9
      75: istore_0
      76: goto          81
      79: iconst_m1
      80: istore_0
      81: return

  static {};
    Code:
       0: iconst_1
       1: invokestatic  #25 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: putstatic     #15 // Field _NUM:Ljava/lang/Integer;
       7: return
}
Copy the code

The most important piece of information in these bytecodes is “getstatic #15”, which means to extract the “_NUM” variable and condition for judgment.

As can be seen from the above bytecode, variables and conditions are only taken out once in switch for comparison, while variables and conditions are taken out every time in IF for comparison, so if is much slower than switch.

Increase test volume

The previous test code used five branch criteria to test if and switch performance. What if we multiplied the branch criteria by three times (15)?

The implementation code for increasing to 15 branch judgments is as follows:

package com.example.optimize; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; @benchmarkMode (mode.averageTime) // Test completion time @outputTimeUnit (timeunit.nanoseconds) @Warmup(iterations = 2, time = 1, TimeUnit = timeunit.seconds) Iterations = 5 @Measurement(time = 1, timeUnit = timeunit.seconds) // Perform 5 iterations. 3s@fork (1) // Fork 1 Thread @state (scope.thread) // One instance of each test Thread Public Class SwitchOptimizeTest {static Integer _NUM = 1; Public static void main(String[] args) throws RunnerException {// Start the benchmark. Options opt = new OptionsBuilder() Include (SwitchOptimizeTest. Class. GetSimpleName ()) / / to import the test class. The output ("/Users/admin/Desktop/jmh-switch.log") // Output test result.build (); new Runner(opt).run(); // Perform a test} @benchmark public voidswitchTest() {
        int num1;
        switch (_NUM) {
            case 1:
                num1 = 1;
                break;
            case 2:
                num1 = 2;
                break;
            case 3:
                num1 = 3;
                break;
            case 4:
                num1 = 4;
                break;
            case 5:
                num1 = 5;
                break;
            case 6:
                num1 = 6;
                break;
            case 7:
                num1 = 7;
                break;
            case 8:
                num1 = 8;
                break;
            case 9:
                num1 = 9;
                break;
            case 10:
                num1 = 10;
                break;
            case 11:
                num1 = 11;
                break;
            case 12:
                num1 = 12;
                break;
            case 13:
                num1 = 13;
                break;
            case 14:
                num1 = 14;
                break;
            case 15:
                num1 = 15;
                break;
            default:
                num1 = -1;
                break;
        }
    }

    @Benchmark
    public void ifTest() {
        int num1;
        if (_NUM == 1) {
            num1 = 1;
        } else if (_NUM == 2) {
            num1 = 2;
        } else if (_NUM == 3) {
            num1 = 3;
        } else if (_NUM == 4) {
            num1 = 4;
        } else if (_NUM == 5) {
            num1 = 5;
        } else if (_NUM == 6) {
            num1 = 6;
        } else if (_NUM == 7) {
            num1 = 7;
        } else if (_NUM == 8) {
            num1 = 8;
        } else if (_NUM == 9) {
            num1 = 9;
        } else if (_NUM == 10) {
            num1 = 10;
        } else if (_NUM == 11) {
            num1 = 11;
        } else if (_NUM == 12) {
            num1 = 12;
        } else if (_NUM == 13) {
            num1 = 13;
        } else if (_NUM == 14) {
            num1 = 14;
        } else if (_NUM == 15) {
            num1 = 15;
        } else{ num1 = -1; }}}Copy the code

The test results for the above code are as follows:

As can be seen from the Score value, when branch judgments are increased to 15, the performance of Switch is about 3.7 times higher than that of IF, while the previous test result when there are 5 branch judgments is about 2.3 times higher than that of IF. In other words, the more criteria for branching, the more obvious the switch’s high performance will be.

The secret of the switch

For the switch, the generated bytecode has two types: tableswitch and lookupSwitch. The type of bytecode used in the generated code depends on how compact the switch adds the code, for example, to case 1… 2… 3… 4 Tableswitch is used in ascending order. For example, if case is 1… 33… 55… This non-compact condition is tested using lookupSwitch as follows:

public class SwitchOptimize {
    static Integer _NUM = 1;
    public static void main(String[] args) {
        tableSwitchTest();
        lookupSwitchTest();
    }
    public static void tableSwitchTest() {
        int num1;
        switch (_NUM) {
            case 1:
                num1 = 1;
                break;
            case 2:
                num1 = 2;
                break;
            case 3:
                num1 = 3;
                break;
            case 4:
                num1 = 4;
                break;
            case 5:
                num1 = 5;
                break;
            case 6:
                num1 = 6;
                break;
            case 7:
                num1 = 7;
                break;
            case 8:
                num1 = 8;
                break;
            case 9:
                num1 = 9;
                break;
            default:
                num1 = -1;
                break;
        }
    }
    public static void lookupSwitchTest() {
        int num1;
        switch (_NUM) {
            case 1:
                num1 = 1;
                break;
            case 11:
                num1 = 2;
                break;
            case 3:
                num1 = 3;
                break;
            case 4:
                num1 = 4;
                break;
            case 19:
                num1 = 5;
                break;
            case 6:
                num1 = 6;
                break;
            case 33:
                num1 = 7;
                break;
            case 8:
                num1 = 8;
                break;
            case 999:
                num1 = 9;
                break;
            default:
                num1 = -1;
                break; }}}Copy the code

The corresponding bytecode is as follows:

public class com.example.optimize.SwitchOptimize {
  static java.lang.Integer _NUM;

  public com.example.optimize.SwitchOptimize();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."
      
       ":()V
      
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #7 // Method tableSwitchTest:()V
       3: invokestatic  #12 // Method lookupSwitchTest:()V
       6: return

  public static void tableSwitchTest();
    Code:
       0: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
       3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
       6: tableswitch   { // 1 to 9
                     1: 56
                     2: 61
                     3: 66
                     4: 71
                     5: 76
                     6: 81
                     7: 87
                     8: 93
                     9: 99
               default: 105
          }
      56: iconst_1
      57: istore_0
      58: goto          107
      61: iconst_2
      62: istore_0
      63: goto          107
      66: iconst_3
      67: istore_0
      68: goto          107
      71: iconst_4
      72: istore_0
      73: goto          107
      76: iconst_5
      77: istore_0
      78: goto          107
      81: bipush        6
      83: istore_0
      84: goto          107
      87: bipush        7
      89: istore_0
      90: goto          107
      93: bipush        8
      95: istore_0
      96: goto          107
      99: bipush        9
     101: istore_0
     102: goto          107
     105: iconst_m1
     106: istore_0
     107: return

  public static void lookupSwitchTest();
    Code:
       0: getstatic     #15 // Field _NUM:Ljava/lang/Integer;
       3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
       6: lookupswitch  { // 9
                     1: 88
                     3: 98
                     4: 103
                     6: 113
                     8: 125
                    11: 93
                    19: 108
                    33: 119
                   999: 131
               default: 137
          }
      88: iconst_1
      89: istore_0
      90: goto          139
      93: iconst_2
      94: istore_0
      95: goto          139
      98: iconst_3
      99: istore_0
     100: goto          139
     103: iconst_4
     104: istore_0
     105: goto          139
     108: iconst_5
     109: istore_0
     110: goto          139
     113: bipush        6
     115: istore_0
     116: goto          139
     119: bipush        7
     121: istore_0
     122: goto          139
     125: bipush        8
     127: istore_0
     128: goto          139
     131: bipush        9
     133: istore_0
     134: goto          139
     137: iconst_m1
     138: istore_0
     139: return

  static {};
    Code:
       0: iconst_1
       1: invokestatic  #25 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: putstatic     #15 // Field _NUM:Ljava/lang/Integer;
       7: return
}
Copy the code

According to the preceding bytecode, tableSwitchTest uses TablesWitch, and lookupSwitchTest uses LookupSwitch.

tableswitch VS lookupSwitchTest

When tableswitch is executed once, the int value at the top of the stack is used directly as the index in the table to grab the jump target and perform the jump immediately. That is to say, tableswitch has a storage structure similar to that of an array and obtains elements directly by index. Therefore, the time complexity of the entire query is O(1), which means that the search speed is very fast.

When lookupswitch is executed, branches are compared one by one or dichotomy is used for query. Therefore, the query time is O(log N). Lookupswitch is slower than tablesWitch.

Next, let’s test the performance between them using the actual code as follows:

package com.example.optimize; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; @benchmarkMode (mode.averageTime) // Test completion time @outputTimeUnit (timeunit.nanoseconds) @Warmup(iterations = 2, time = 1, TimeUnit = timeunit.seconds) Iterations = 5 @Measurement(time = 1, timeUnit = timeunit.seconds) // Perform 5 iterations. 3s@fork (1) // Fork 1 Thread @state (scope.thread) // One instance of each test Thread Public Class SwitchOptimizeTest {static Integer _NUM = - 1; Public static void main(String[] args) throws RunnerException {// Start the benchmark. Options opt = new OptionsBuilder() Include (SwitchOptimizeTest. Class. GetSimpleName ()) / / to import the test class. The build (); new Runner(opt).run(); // Perform a test} @benchmark public voidtableSwitchTest() {
        int num1;
        switch (_NUM) {
            case 1:
                num1 = 1;
                break;
            case 2:
                num1 = 2;
                break;
            case 3:
                num1 = 3;
                break;
            case 4:
                num1 = 4;
                break;
            case 5:
                num1 = 5;
                break;
            case 6:
                num1 = 6;
                break;
            case 7:
                num1 = 7;
                break;
            case 8:
                num1 = 8;
                break;
            case 9:
                num1 = 9;
                break;
            default:
                num1 = -1;
                break;
        }
    }

    @Benchmark
    public void lookupSwitchTest() {
        int num1;
        switch (_NUM) {
            case 1:
                num1 = 1;
                break;
            case 11:
                num1 = 2;
                break;
            case 3:
                num1 = 3;
                break;
            case 4:
                num1 = 4;
                break;
            case 19:
                num1 = 5;
                break;
            case 6:
                num1 = 6;
                break;
            case 33:
                num1 = 7;
                break;
            case 8:
                num1 = 8;
                break;
            case 999:
                num1 = 9;
                break;
            default:
                num1 = -1;
                break; }}}Copy the code

The test results for the above code are as follows:

If the number of branches is nine, tableswitch performance is 1.3 times faster than lookupwitch performance. But even then lookupwitch still performs much better than if queries.

conclusion

Switch performed about 2.3 times better than if when the number of criteria was five, and the difference in performance was greater when the number of criteria increased. When the switch compiles to bytecode, it generates two types of code according to the switch’s criteria: Tableswitch (generated when compact) and lookupswitch (generated when non-compact). Tableswitch uses a storage structure similar to an array and queries elements based on indexes. Lookupswitch is queried one by one or using dichotomy. Therefore, tableswitch has higher performance than LookupSwitch, but switch has higher performance than IF in any case.

The last word

Original is not easy, if you think this article is useful to you, please click on a “like”, this is the biggest support and encouragement to the author, thank you.

Reference & acknowledgements

www.javaguides.net/2020/03/5-b…

Follow the public account “Java Chinese community” reply “dry goods”, obtain 50 original dry goods Top list.