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.