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,一经查实,立即删除!

相关文章

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

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

【Android开发】之Fragment与Acitvity通信

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

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

这是磊哥的第 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…

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")…

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

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

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

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

关于引用

2019独角兽企业重金招聘Python工程师标准>>> 1、 <!-- lang: html --> <!DOCTYPE HTML> <!-- lang: html --> <html> <!-- lang: html --> <head> <!-- lang: html --> <meta charset"utf-8" /> <!--…

一口气说出 6 种延时队列的实现方法,面试官满意的笑了

这是我的第 193 期分享作者 | 程序员内点事来源 | 程序员内点事&#xff08;ID&#xff1a;chegnxy-nds&#xff09; 分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;五一期间原计划是写两篇文章&#xff0c;看一本技术类书籍&#xff0c;结果这五天由于自…

acl 服务器编程框架特点介绍

2019独角兽企业重金招聘Python工程师标准>>> acl 中服务器框架模块是一个非常重要的模块&#xff0c;使用该模块技术人员可以快速地写出稳定、安全、高效的网络服务应用&#xff0c;该模块主要来源于著名的邮件服务器程序 (Postfix) 中的 master 模块&#xff0c;为…

人人都能看懂的 6 种限流实现方案!(纯干货)

这是我的第 195 期分享作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;为了上班方便&#xff0c;去年我把自己在北郊的房子租出去了&#xff0c;搬到了南郊&#xff0c;这样…

測试新浪微博@小冰 为代码机器人的一些方法

微软的微信小冰被腾讯封杀之后,如今移民到了新浪微博; 小冰 这里贴一些眼下有效的用来识别是这是"机器"而不是有正常人类智商的代码的方法: 1. 在正常的文字中夹杂其他符号,确保不存在有意义的连续的词汇,人眼能够分辨,机器不知所云而会露馅: 比方: ^^^小v冰^^^-…

秒建一个后台管理系统?用这5个开源免费的Java项目就够了

这是我的第 196 期分享作者 | Guide来源 | JavaGuide&#xff08;ID&#xff1a;JavaGuide&#xff09; 分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;大家好&#xff0c;我是 Guide 哥&#xff0c;一个三观比主角还正的技术人。今天推荐几个 Java 项目…

读《白帽子讲Web安全》之客户端脚本安全(一)

2019独角兽企业重金招聘Python工程师标准>>> 【第2章 浏览器安全】 1、同源策略&#xff08;Same Origin Policy&#xff09;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能。 浏览器的同源策略&#xff0c;限制了来自不同源的“document”或脚本&…

RocketMQ一行代码造成消息发送失败

这是我的第 198 期分享作者 | 丁威来源 | 中间件兴趣圈&#xff08;ID&#xff1a;dingwpmz_zjj&#xff09;分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;1、问题现象首先接到项目反馈使用 RocketMQ 会出现如下错误&#xff1a;错误信息关键点&#xf…

Java 中的 String 有没有长度限制?

这是我的第 199 期分享作者 | Hollis来源 | Hollis&#xff08;ID&#xff1a;hollischuang&#xff09; 分享 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;关于String有没有长度限制的问题&#xff0c;我之前单独写过一篇文章分析过&#xff0c;最近我又抽…

开源 免费 java CMS - FreeCMS2.1 菜单管理

2019独角兽企业重金招聘Python工程师标准>>> 项目地址&#xff1a;http://www.freeteam.cn/ 菜单管理 FreeCMS在设计时定位于面向二次开发友好&#xff0c;所以FreeCMS提供了菜单管理功能&#xff0c;二次开发人员可以自由增加新的功能菜单到FreeCMS。 为了让后台…

本来想用“{{”秀一波,结果却导致了内存溢出!

这是我的第 200 期分享作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;生活中的尴尬无处不在&#xff0c;有时候你只是想简单的装一把&#xff0c;但某些“老同志”总是在不…

局部变量竟然比全局变量快 5 倍?

这是我的第 201 期分享作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;喽&#xff0c;大家好&#xff0c;磊哥的性能优化篇又来了&#xff01;其实写这个性能优化类的文章初…