if else和switch比较谁更快

    技术2023-05-15  104

    我们借助 Oracle 官方提供的 JMH(JAVA 微基准测试套件)框架来进行测试.

    代码如下

    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)//测试完成时间 @OutputTimeUnit(TimeUnit.SECONDS) @Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s @Fork(1)//fork 一个线程 @State(Scope.Thread)//每个线程一个实例 public class TestIfSwich { static int caseNum = 5; public static void main(String[] args) throws RunnerException { //建造者模式启动基准测试 Options options = new OptionsBuilder().include(TestIfSwich.class.getSimpleName()).output("C:\\Users\\Administrator\\IdeaProjects\\zzz\\src\\main\\resources\\ifswich2.log").build(); new Runner(options).run(); } @Benchmark public void switchTest(){ int num1; switch (caseNum){ 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; } } @Benchmark public void ifTest(){ int num1; if(caseNum ==1){ num1=1; }else if(caseNum ==2){ num1=2; }else if(caseNum ==3){ num1=3; }else if(caseNum ==4){ num1=4; }else if(caseNum ==5) { num1 = 5; }else { num1=-1; } } }

    读取写出的日志查看跑出来的结果如下图所示

    Benchmark Mode Cnt Score Error Units TestIfSwich.ifTest avgt 5 ≈ 10⁻⁸ s/op TestIfSwich.switchTest avgt 5 ≈ 10⁻⁹ s/op

    if的速度比switch慢了十倍,环境为windows本地,jdk11

    注意:

    Exception in thread "main" java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList at org.openjdk.jmh.runner.AbstractResourceReader.getReaders(AbstractResourceReader.java:98) at org.openjdk.jmh.runner.BenchmarkList.find(BenchmarkList.java:122) at org.openjdk.jmh.runner.Runner.internalRun(Runner.java:263) at org.openjdk.jmh.runner.Runner.run(Runner.java:209) at com.test.java8.LongAdderTest.main(LongAdderTest.java:27)

    开始在pom文件里只引入了如下导致上面报错

    <dependency>    <groupId>org.openjdk.jmh</groupId>    <artifactId>jmh-core</artifactId>    <version>1.23</version> </dependency>

    通过再次添加如下依赖解决

    <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.23</version> <scope>provided</scope> </dependency>

    为了一探究竟为什么switch比if快,我们查看编译后的字节码通过命令javap -c D:\***.java

    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."<init>":()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 }

    发现if里面多次调用getstatic积少成多,if调用会被switch快。

    还有就是switch解析过来是tableswitch,如果switch里面case的数字不是递增,而是乱序或者随机增加的,解析过来就是lookupswitch

    tableswitch 和 lookupSwitchTest 当执行一次 tableswitch 时,堆栈顶部的 int 值直接用作表中的索引,以便抓取跳转目标并立即执行跳转。也就是说 tableswitch 的存储结构类似于数组,是直接用索引获取元素的,所以整个查询的时间复杂度是 O(1),这也意味着它的搜索速度非常快。

    而执行 lookupswitch 时,会逐个进行分支比较或者使用二分法进行查询,因此查询时间复杂度是 O(log n),所以使用 lookupswitch 会比 tableswitch 慢  

    Processed: 0.017, SQL: 9