Java基础进阶系列-03之JMH分析if条件判断语句与switch性能差异

  • Post author:
  • Post category:java




JMH分析if与switch性能差异


解决问题的本质就是回归源码和阅读说明文档



背景说明

我们知道switch-case结构,当case值是“密集”时,底层指令会优化为tableswitch结构,执行效率上要比“稀疏”时的lookupswitch好。


Java 虚拟机规范 基于JDK8->3.10 编译switch章节

编译器会使用tableswitch和lookupswitch指令来生成switch语句的编译代码。

tableswitch指令用于表示switch结构中的case语句块,它可以高效地从索引表中确定case语句块的分支偏移量。

当switch语句中的条件值不能对应于索引表中任何一个case语句块的分支偏移量时,default分支将起作用。

当switch语句中的case分支条件值比较稀疏时,tableswitch指令的空间使用率偏低。

该情况下可以使用lookupswitch指令来替代。

lookupswitch指令的索引表项由int类型的键(来源于case语句块后面的数值)与对应的目标语句偏移量所构成。

当lookupswitch指令执行时,switch语句的条件值将和索引表中的键进行比较,如果某个键和条件值相符,那么将转移到这个键所对应的分支偏移量继续执行,如果没有键值符合,将执行default分支。

在从索引表确定分支偏移量的过程中,lookupswitch指令必须把条件值与不同的键进行比较,而tablesswitch指令则只需要对索引值进行一次范围检查。

因此,如果不太需要考虑空间效率,那么tableswitch指令会比lookupswitch指令有更高的执行效率。

  • 我们将if条件判断语句进行反编译操作,看看底层是如何执行的

    public static void main(String[] args) {
         
        int a = 4;
        if (1 == a) {
         
            System.out.println(1);
        } else if (2 == a) {
         
            System.out.println(2);
        } else if (3 == a) {
         
            System.out.println(3);
        } else {
         
            System.out.println(4);
        }
    }
    
    • javap -c 字节码文件
    public static void main(java.lang.String[]);
        Code:
    	   // 将int型4推送至栈顶
           0: iconst_4
    	   // 将栈顶int型数值存入第1个本地变量
           1: istore_1
    	   // 将int型1推送至栈顶
           2: iconst_1
    	   // 将第1个int型本地变量推送至栈顶
           3: iload_1
    	   // 比较栈顶两个int型数值大小,当结果不等于0时,就跳到第17个指令继续执行
           4: if_icmpne     17
           // 获取指定类的静态域,并将其值压入栈顶
    	   7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           // 将int型1推送至栈顶
    	  10: iconst_1
           // 调用实例方法
    	  11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
          // 无条件跳转
    	  14: goto          54
          17: iconst_2
          18: iload_1
          19: if_icmpne     32
          22: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
          25: iconst_2
          26: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
          29: goto          54
          32: iconst_3
          33: iload_1
          34: if_icmpne     47
          37: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
          40: iconst_3
          41: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
          44: goto          54
          47: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
          50: iconst_4
          51: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
          54: return
    }
    

    由上面的反编译内容,我们可以看到if条件判断语句实际上是逐个条件进行判断,也就说,可能出现不断比较直到最后一个条件语句才匹配的情况。



JMH 微基准测试 验证性能效率


OpenJDK JMH文档

JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targetting the JVM.

JMH is not intended to be used in the same way as a typical testing library such as JUnit. Simply adding the

jmh-core

jar file to your build is not enough to be able to run benchmarks

JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。

主要是基于方法层面的基准测试,精度可达纳秒级别。

当希望进一步优化方法性能的时候,就可以使用JMH对优化结果进行量化分析。



引入依赖

<properties>
    <jmh.version>1.25</jmh.version>
</properties>

<dependencies>
	<!-- JMH基准测试框架依赖 -->
	<!-- The jmh is a Java harness for building, running,
	and analysing nano/micro/macro benchmarks written in Java and other languages targeting the JVM. -->
	<dependency>
		<groupId>org.openjdk.jmh</groupId>
		<artifactId>jmh-core</artifactId>
		<version>${jmh.version}</version>
	</dependency>
	<!-- JMH benchmark generator, based on annotation processors. -->
	<dependency>
		<groupId>org.openjdk.jmh</groupId>
		<artifactId>jmh-generator-annprocess</artifactId>
		<version>${jmh.version}</version>
		<scope>provided</scope>
	</dependency>



版权声明:本文为Mr_Mocha原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。