if快还是switch快?解密switch背后的秘密

这是我的第 57 篇原创文章

条件判断语句是程序的重要组成部分,也是系统业务逻辑的控制手段。重要程度和使用频率更是首屈一指,那我们要如何选择 if 还是 switch 呢?他们的性能差别有多大?switch 性能背后的秘密是什么?接下来让我们一起来寻找这些问题的答案。

switch VS if

我在之前的文章《9个小技巧让你的 if else看起来更优雅》中有提过,要尽量使用 switch 因为他的性能比较高,但具体高多少?以及为什么高的原因将在本文为你揭晓。

我们依然借助 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)框架来进行测试,首先引入 JMH 框架,在 pom.xml 文件中添加如下配置:

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

然后编写测试代码,我们这里添加 5 个条件判断分支,具体实现代码如下:

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.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeTest {static Integer _NUM = 9;public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(SwitchOptimizeTest.class.getSimpleName()) // 要导入的测试类.output("/Users/admin/Desktop/jmh-switch.log") // 输出测试结果的文件.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic void switchTest() {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;}}@Benchmarkpublic 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;}}
}

以上代码的测试结果如下:


备注:本文的测试环境为:JDK 1.8 / Mac mini (2018) / Idea 2020.1

从以上结果可以看出(Score 列),switch 的平均执行完成时间比 if 的平均执行完成时间快了约 2.33 倍

性能分析

为什么 switch 的性能会比 if 的性能高这么多?

这需要从他们字节码说起,我们把他们的代码使用 javac 生成字节码如下所示:

public class com.example.optimize.SwitchOptimize {static java.lang.Integer _NUM;public com.example.optimize.SwitchOptimize();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: invokestatic  #7                  // Method switchTest:()V3: invokestatic  #12                 // Method ifTest:()V6: returnpublic static void switchTest();Code:0: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;3: invokevirtual #19                 // Method java/lang/Integer.intValue:()I6: tableswitch   { // 1 to 91: 562: 833: 614: 835: 666: 837: 718: 839: 77default: 83}56: iconst_157: istore_058: goto          8561: iconst_362: istore_063: goto          8566: iconst_567: istore_068: goto          8571: bipush        773: istore_074: goto          8577: bipush        979: istore_080: goto          8583: iconst_m184: istore_085: returnpublic static void ifTest();Code:0: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;3: invokevirtual #19                 // Method java/lang/Integer.intValue:()I6: iconst_17: if_icmpne     1510: iconst_111: istore_012: goto          8115: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;18: invokevirtual #19                 // Method java/lang/Integer.intValue:()I21: iconst_322: if_icmpne     3025: iconst_326: istore_027: goto          8130: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;33: invokevirtual #19                 // Method java/lang/Integer.intValue:()I36: iconst_537: if_icmpne     4540: iconst_541: istore_042: goto          8145: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;48: invokevirtual #19                 // Method java/lang/Integer.intValue:()I51: bipush        753: if_icmpne     6256: bipush        758: istore_059: goto          8162: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;65: invokevirtual #19                 // Method java/lang/Integer.intValue:()I68: bipush        970: if_icmpne     7973: bipush        975: istore_076: goto          8179: iconst_m180: istore_081: returnstatic {};Code:0: iconst_11: invokestatic  #25                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: putstatic     #15                 // Field _NUM:Ljava/lang/Integer;7: return
}

这些字节码中最重要的信息是“getstatic     #15”,这段代码表示取出“_NUM”变量和条件进行判断。

从上面的字节码可以看出,在 switch 中只取出了一次变量和条件进行比较,而 if 中每次都会取出变量和条件进行比较,因此 if 的效率就会比 switch 慢很多

提升测试量

前面的测试代码我们使用了 5 个分支条件来测试了 if 和 switch 的性能,那如果把分支的判断条件增加 3 倍(15 个)时,测试的结果又会怎么呢?

增加至 15 个分支判断的实现代码如下:

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) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeTest {static Integer _NUM = 1;public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(SwitchOptimizeTest.class.getSimpleName()) // 要导入的测试类.output("/Users/admin/Desktop/jmh-switch.log") // 输出测试结果的文件.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic void switchTest() {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;}}@Benchmarkpublic 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;}}
}

以上代码的测试结果如下:


从 Score 的值可以看出,当分支判断增加至 15 个,switch 的性能比 if 的性能高出了约 3.7 倍,而之前有 5 个分支判断时的测试结果为,switch 的性能比 if 的性能高出了约 2.3 倍,也就是说分支的判断条件越多,switch 性能高的特性体现的就越明显

switch 的秘密

对于 switch 来说,他最终生成的字节码有两种形态,一种是 tableswitch,另一种是 lookupswitch,决定最终生成的代码使用那种形态取决于 switch 的判断添加是否紧凑,例如到 case 是 1...2...3...4 这种依次递增的判断条件时,使用的是 tableswitch,而像 case 是 1...33...55...22 这种非紧凑型的判断条件时则会使用 lookupswitch,测试代码如下:

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;}}
}

对应的字节码如下:

public class com.example.optimize.SwitchOptimize {static java.lang.Integer _NUM;public com.example.optimize.SwitchOptimize();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: invokestatic  #7                  // Method tableSwitchTest:()V3: invokestatic  #12                 // Method lookupSwitchTest:()V6: returnpublic static void tableSwitchTest();Code:0: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;3: invokevirtual #19                 // Method java/lang/Integer.intValue:()I6: tableswitch   { // 1 to 91: 562: 613: 664: 715: 766: 817: 878: 939: 99default: 105}56: iconst_157: istore_058: goto          10761: iconst_262: istore_063: goto          10766: iconst_367: istore_068: goto          10771: iconst_472: istore_073: goto          10776: iconst_577: istore_078: goto          10781: bipush        683: istore_084: goto          10787: bipush        789: istore_090: goto          10793: bipush        895: istore_096: goto          10799: bipush        9101: istore_0102: goto          107105: iconst_m1106: istore_0107: returnpublic static void lookupSwitchTest();Code:0: getstatic     #15                 // Field _NUM:Ljava/lang/Integer;3: invokevirtual #19                 // Method java/lang/Integer.intValue:()I6: lookupswitch  { // 91: 883: 984: 1036: 1138: 12511: 9319: 10833: 119999: 131default: 137}88: iconst_189: istore_090: goto          13993: iconst_294: istore_095: goto          13998: iconst_399: istore_0100: goto          139103: iconst_4104: istore_0105: goto          139108: iconst_5109: istore_0110: goto          139113: bipush        6115: istore_0116: goto          139119: bipush        7121: istore_0122: goto          139125: bipush        8127: istore_0128: goto          139131: bipush        9133: istore_0134: goto          139137: iconst_m1138: istore_0139: returnstatic {};Code:0: iconst_11: invokestatic  #25                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: putstatic     #15                 // Field _NUM:Ljava/lang/Integer;7: return
}

从上面字节码可以看出 tableSwitchTest 使用的 tableswitch,而 lookupSwitchTest 则是使用的 lookupswitch。

tableswitch VS lookupSwitchTest

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

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

接下来我们使用实际的代码测试一下,他们两个之间的性能,测试代码如下:

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) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeTest {static Integer _NUM = -1;public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(SwitchOptimizeTest.class.getSimpleName()) // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic 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;}}@Benchmarkpublic 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;}}
}

以上代码的测试结果如下:


可以看出在分支判断为 9 个时,tableswitch 的性能比 lookupwitch 的性能快了约 1.3 倍。但即使这样 lookupwitch 依然比 if 查询性能要高很多

总结

switch 的判断条件是 5 个时,性能比 if 高出了约 2.3 倍,而当判断条件的数量越多时,他们的性能相差就越大。而 switch 在编译为字节码时,会根据 switch 的判断条件是否紧凑生成两种代码:tableswitch(紧凑时生成)和 lookupswitch(非紧凑时生成),其中 tableswitch 是采用类似于数组的存储结构,直接根据索引查询元素;而 lookupswitch 则需要逐个查询或者使用二分法查询,因此 tableswitch 的性能会比 lookupswitch 的性能高,但无论如何 switch 的性能都比 if 的性能要高

最后的话

原创不易,如果觉得本文对你有用,请随手点击一个「」,这是对作者最大的支持与鼓励,谢谢你。

参考 & 鸣谢

https://www.javaguides.net/2020/03/5-best-ways-to-iterate-over-hashmap-in-java.html

HashMap 的 7 种遍历方式与性能分析!「修正篇」

String性能提升10倍的几个方法!(源码+原理分析)

关注公众号「Java中文社群」回复“干货”,获取原创干货 Top 榜

关注公众号发送”进群“,老王拉你进读者群。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/546162.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL replace into (insert into 的增强版)

在使用SQL语句进行数据表插入insert操作时&#xff0c;如果表中定义了主键&#xff0c;插入具有相同主键的记录会报错&#xff1a; Error Code: 1062. Duplicate entry XXXXX for key PRIMARY(主键冲突) 这样我们只好检查主键是不是存在&#xff0c;若存在则更新&#xff0c;若…

最大连续子序列和问题

问题描述&#xff1a;给定一个序列a[1],a[2]...a[n]&#xff0c;求解其连续子序列中元素和的最大值 例如&#xff1a; 6 -1 5 4 -7 这个序列最大连续子序列和为14 具体问题见&#xff1a; [urlhttp://acm.hdu.edu.cn/showproblem.php?pid1003]HDOJ 1003[/url] [urlhttp://acm.…

js 数组添加n次相同元素_数组中两次出现相同元素之间的最大距离

js 数组添加n次相同元素Prerequisite: Hashing data structure 先决条件&#xff1a; 哈希数据结构 Problem statement: 问题陈述&#xff1a; Find maximum distance between two occurrences of same element in the array. 查找两次出现的相同元素在数组中的最大距离。 E…

一道题决定去留:为什么synchronized无法禁止指令重排,却能保证有序性?

前几天有一位读者找我问一个问题&#xff0c;说是这道题可能影响了他接下来3年的技术成长。据说这位读者前面的很多问题会的都还可以&#xff0c;属于那种可过可不过的类型的&#xff0c;面试官出了最后一道题&#xff0c;就是回答的满意就可以给Offer&#xff0c;回答的不好就…

haskell程序设计语言

根据[urlhttp://www.haskell.org/haskellwiki/Haskell]haskell[/url]的[urlhttp://www.haskell.org/haskellwiki/Introduction]官方定义[/url]&#xff0c;haskell是polymorphically(多态&#xff09; statically typed静态类型&#xff09;, lazy&#xff08;懒计算&#xff0…

【Android开发】之Fragment与Acitvity通信

上一篇我们讲到与Fragment有关的常用函数&#xff0c;既然Fragment被称为是“小Activity”&#xff0c;现在我们来讲一下Fragment如何与Acitivity通信。如果上一篇还有不懂得&#xff0c;可以再看一下。传送门。 Fragment与Activity通信的方式如下&#xff1a; 一、通过初始化函…

next和hasnext_使用Java中的next()和hasNext()方法遍历List元素

next和hasnextGiven a List of integers and we have to traverse, print all its element using next() and hasNext() methods. 给定一个整数列表&#xff0c;我们必须遍历&#xff0c;使用next()和hasNext()方法打印其所有元素。 什么是hasNex()和next()方法&#xff1f; (…

「递归」的正确打开方式,看不懂你打我~

这是磊哥的第 189 期分享作者 | 田小齐来源 | 码农田小齐&#xff08;ID&#xff1a;NYCSDE&#xff09; 分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;前言 递归&#xff0c;是一个非常重要的概念&#xff0c;也是面试中非常喜欢考的。因为它不但能考察…

Log4cpp 使用手册

参考资料&#xff1a; log4cpp 配置 与 使用http://www.cnblogs.com/welkinwalker/archive/2011/06/23/2088197.html 便利的开发工具-log4cpp快速使用指南 http://www.ibm.com/developerworks/cn/linux/l-log4cpp/ Log4cpp配置文件格式说明 http://sogo6.iteye.com/blog/115431…

适合初学编程的一些项目

学习了一门语言以及数据结构之后&#xff0c;通常需要做一些项目来巩固所学的知识&#xff0c;我感觉最好是用写一些简单的小工具或者小游戏&#xff0c;能够提高自己的编程能力&#xff0c;也能进一步提高自己学习的兴趣。最好的是将自己想做的事情用程序的实现&#xff0c;比…

python 5的倍数_查找所有低于1000的数字的和,这是Python中3或5的倍数

python 5的倍数Sometimes, we need to find the sum of all integers or numbers that are completely divisible by 3 and 5 up to thousands, since thousands are a too large number that’s why it becomes difficult for us. So, here we will do it in Python programmi…

switch 的性能提升了 3 倍,我只用了这一招!

这是我的第 190 期分享作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09; 分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;上一篇《if快还是switch快&#xff1f;解密switch背后的秘密》我们测试了 if 和 switch 的性能&am…

HashMap get不出对象时出错 解决

为什么80%的码农都做不了架构师&#xff1f;>>> 如题&#xff1a; Map map new HashMap(); map.put("1", "A"); map.put("2", "A"); map.put("3", "A"); map.put("4", "A")…

安装codeblocks和wxwidgets及opencv

codeblocks 是一款开放源代码的跨平台的c/c++集成开发环境,它是用wxwidgets 开发的,并且支持插件,功能很强大。可以用来在windows开发各种程序。下面记录一下我安装该软件的过程: 首先到http://www.codeblocks.org/downloads 下载该软件,选择含mingw的文件(codeblocks-10…

Java LinkedList boolean addAll(int index,Collection c)方法,带有示例

LinkedList boolean addAll(int index&#xff0c;Collection c)方法 (LinkedList boolean addAll(int index, Collection c) method) This method is available in package java.util.Collection and here, Collection is an interface. 该方法在java.util.Collection包中可用…

高质量SQL的30条建议!(后端必备)

这是我的第 191 期分享作者 | 捡田螺的小男孩来源 | 捡田螺的小男孩&#xff08;ID&#xff1a;gh_873ad5979a0b&#xff09; 分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;本文将结合实例demo&#xff0c;阐述30条有关于优化SQL的建议&#xff0c;多数…

滤波电容的选择(调试中)

整个系统高电平时12V&#xff0c;信号是ms级的负脉冲&#xff0c;脉冲的下降沿很陡&#xff0c;当用示波器展开看的时候&#xff0c;发现了低于0V的过冲&#xff0c;能低到-6V&#xff08;相当严重&#xff09;&#xff0c;这是在整个箱子的出口处测量到的。在主控板&#xff0…

sicp

sicp (structure and interpretion of computer programs &#xff0c;编号为6.001 )是麻省理工学院的一门经典计算机入门课程&#xff0c;虽然近年被另外一门课程所取代&#xff08;6.00 或6.01 &#xff09;&#xff0c;但最近好像有复活 的迹象。Perter Norvig 写了关于sicp…

Redis的自白:我为什么在单线程的这条路上越走越远?

这是我的第 192 期分享作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;我是 Redis&#xff0c;今年 11 岁了~曾几何时我是辣么的单纯&#xff0c;辣么的可爱&#xff0c;而如…

两个矩阵相乘的乘法次数_C ++程序将两个数字相乘而不使用乘法运算符

两个矩阵相乘的乘法次数The problem is we have two integer numbers and find the multiplication of them without using the multiplication operator. This problem can be solved using the Russian peasant algorithm. Assume the two given numbers are m and n. Initia…