In our last post on if or Switch, we tested the performance of both if and Switch and came to the conclusion that switch should be used as much as possible because it is much more efficient than if. Click the link above to see why.

If the Switch is so attractive, is there a better way to make it faster?

The answer is yes, otherwise this article would not have been created, would it?

A String switch performs better than an if switch. The String condition indicates that the switch still performs better than if.

Oral without proof, first take 🌰, the test code is as follows:

import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; 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 = 3, timeUnit = timeunit.seconds) // Perform 5 iterations. Every time 3 s 1 (1) / / @ Fork Fork Thread @ State (Scope. Thread) / / each test Thread one instance public class SwitchOptimizeByStringTest {static String _STR ="Java Chinese Community"; Public static void main(String[] args) throws RunnerException {// Start the benchmark. Options opt = new OptionsBuilder() Include (SwitchOptimizeByStringTest. Class. GetSimpleName ()) / / to import the test class. The build (); new Runner(opt).run(); } @benchmark public void switchTest(Blackhole Blackhole) {String s1; switch (_STR) {case "java":
                s1 = "java";
                break;
            case "mysql":
                s1 = "mysql";
                break;
            case "oracle":
                s1 = "oracle";
                break;
            case "redis":
                s1 = "redis";
                break;
            case "mq":
                s1 = "mq";
                break;
            case "kafka":
                s1 = "kafka";
                break;
            case "rabbitmq":
                s1 = "rabbitmq";
                break;
            default:
                s1 = "default";
                break; } // To prevent the JIT from ignoring unused result calculations, use BlackholeConsume to ensure that the method is properly executed
        blackhole.consume(s1);
    }

    @Benchmark
    public void ifTest(Blackhole blackhole) {
        String s1;
        if ("java".equals(_STR)) {
            s1 = "java";
        } else if ("mysql".equals(_STR)) {
            s1 = "mysql";
        } else if ("oracle".equals(_STR)) {
            s1 = "oracle";
        } else if ("redis".equals(_STR)) {
            s1 = "redis";
        } else if ("mq".equals(_STR)) {
            s1 = "mq";
        } else if ("kafka".equals(_STR)) {
            s1 = "kafka";
        } else if ("rabbitmq".equals(_STR)) {
            s1 = "rabbitmq";
        } else {
            s1 = "default"; } // To prevent the JIT from ignoring unused result calculations, use BlackholeConsume to ensure that the method is properly executedblackhole.consume(s1); }}Copy the code

Note: This article uses JMH (Java Microbenchmark Harness, Java Microbenchmark Suite), a performance test tool provided by Oracle.

The results of the above code tests are as follows:

As you can see from the Score column (average completion time), switch still performs better than if.

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

Switch Performance Optimization

We know that prior to JDK 1.7 the Switch did not support strings. In fact, the Switch only supported ints.

In JDK 1.7, hashCode is used as the actual value of the String.

public static void switchTest() {
    String var1 = _STR;
    byte var2 = -1;
    switch(var1.hashCode()) {
        case- 1008861826:if (var1.equals("oracle")) {
                var2 = 2;
            }
            break;
        case- 95168706:if (var1.equals("rabbitmq")) {
                var2 = 6;
            }
            break;
        case 3492:
            if (var1.equals("mq")) {
                var2 = 4;
            }
            break;
        case 3254818:
            if (var1.equals("java")) {
                var2 = 0;
            }
            break;
        case 101807910:
            if (var1.equals("kafka")) {
                var2 = 5;
            }
            break;
        case 104382626:
            if (var1.equals("mysql")) {
                var2 = 1;
            }
            break;
        case 108389755:
            if (var1.equals("redis")) { var2 = 3; }} // Ignore other code... }Copy the code

Knowing the nature of the Switch implementation, optimization becomes relatively simple.

As can be seen from the above bytecode, if you want to optimize the switch, you only need to change the String type into the int type, which will save the performance cost of if judgment in each case. The final optimization code is as follows:

public void switchHashCodeTest() {
    String s1;
    switch (_STR.hashCode()) {
        case 3254818:
            s1 = "java";
            break;
        case 104382626:
            s1 = "mysql";
            break;
        case -1008861826:
            s1 = "oracle";
            break;
        case 108389755:
            s1 = "redis";
            break;
        case 3492:
            s1 = "mq";
            break;
        case 101807910:
            s1 = "kafka";
            break;
        case -95168706:
            s1 = "rabbitmq";
            break;
        default:
            s1 = "default";
            break; }}Copy the code

At this point we use JMH for the actual test, the test code is as follows:

import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; 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 = 3, timeUnit = timeunit.seconds) // Perform 5 iterations. Every time 3 s 1 (1) / / @ Fork Fork Thread @ State (Scope. Thread) / / each test Thread one instance public class SwitchOptimizeByStringTest {static String _STR ="Java Chinese Community"; Public static void main(String[] args) throws RunnerException {// Start the benchmark. Options opt = new OptionsBuilder() Include (SwitchOptimizeByStringTest. Class. GetSimpleName ()) / / to import the test class. The build (); new Runner(opt).run(); } @benchmark public void switchHashCodeTest(Blackhole Blackhole) {String s1; switch (_STR.hashCode()) {case 3254818:
                s1 = "java";
                break;
            case 104382626:
                s1 = "mysql";
                break;
            case -1008861826:
                s1 = "oracle";
                break;
            case 108389755:
                s1 = "redis";
                break;
            case 3492:
                s1 = "mq";
                break;
            case 101807910:
                s1 = "kafka";
                break;
            case -95168706:
                s1 = "rabbitmq";
                break;
            default:
                s1 = "default";
                break; } // To prevent the JIT from ignoring unused result calculations, use BlackholeConsume to ensure that the method is properly executed
        blackhole.consume(s1);
    }

    @Benchmark
    public void switchTest(Blackhole blackhole) {
        String s1;
        switch (_STR) {
            case "java":
                s1 = "java";
                break;
            case "mysql":
                s1 = "mysql";
                break;
            case "oracle":
                s1 = "oracle";
                break;
            case "redis":
                s1 = "redis";
                break;
            case "mq":
                s1 = "mq";
                break;
            case "kafka":
                s1 = "kafka";
                break;
            case "rabbitmq":
                s1 = "rabbitmq";
                break;
            default:
                s1 = "default";
                break; } // To prevent the JIT from ignoring unused result calculations, use BlackholeConsume to ensure that the method is properly executed
        blackhole.consume(s1);
    }

    @Benchmark
    public void ifTest(Blackhole blackhole) {
        String s1;
        if ("java".equals(_STR)) {
            s1 = "java";
        } else if ("mysql".equals(_STR)) {
            s1 = "mysql";
        } else if ("oracle".equals(_STR)) {
            s1 = "oracle";
        } else if ("redis".equals(_STR)) {
            s1 = "redis";
        } else if ("mq".equals(_STR)) {
            s1 = "mq";
        } else if ("kafka".equals(_STR)) {
            s1 = "kafka";
        } else if ("rabbitmq".equals(_STR)) {
            s1 = "rabbitmq";
        } else {
            s1 = "default"; } // To prevent the JIT from ignoring unused result calculations, use BlackholeConsume to ensure that the method is properly executedblackhole.consume(s1); }}Copy the code

The results of the above code tests are as follows:

As can be seen from the above results, the performance of the String switch is improved by 2.4 times after optimization, which can be described as a significant effect.

Matters needing attention

The above switch optimization is based on String type. Meanwhile, we need to pay attention to the problem of duplicate hashCode. For example, for the String “Aa” and “BB”, their hashCode is 2112, so we need to pay attention to this problem in optimization. That is, when we use hashCode, we have to make sure that the values we add are known, and it is best not to have the problem of duplicate hashCode. If such a problem occurs, our solution is to judge and assign values in case.

Other optimization methods

This article focuses on switch performance optimization, but for performance reasons, we can also use more efficient alternatives such as collections or enumerations, as described in my article 9 Tips to Make Your If Else Look elegant.

conclusion

As you can see from this article, the Switch essentially only supports the conditional judgment of the int type. Even the String in JDK 1.7 will be converted to hashCode (int) when it is compiled. However, since bytecode is compiled to compare if equals in case, the performance is not too high (only slightly higher than if), so we can directly convert String to int for comparison. To avoid the performance cost of checking if equals in case, which greatly improves switch performance, but it should be noted that some key values have the same hashCode, so it should be avoided in advance during optimization.

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.

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