热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

这是我的第190期分享作者|王磊来源|Java中文社群(ID:javacn666)分享|Java中文社群(ID:

这是我的第 190 期分享

作者 | 王磊

来源 | Java中文社群(ID:javacn666) 

分享 | Java中文社群(ID:javacn666)

上一篇《if快还是switch快?解密switch背后的秘密》我们测试了 if 和 switch 的性能,得出了要尽量使用 switch 的结论,因为他的效率比 if 高很多,具体原因点击上文连接查看。

既然 switch 如此有魅力,那么有没有更好的方法,让 switch 变得更快一些呢?

答案是有的,不然本文就不会诞生了不是?

在上篇 if 和 switch 性能对比的文章中有读者问到:String 类型的 switch 性能是否也比 if 高?先说答案,String 类型的条件判断 switch 的性能依旧比 if 好

口说无凭,先举个????,测试代码如下:

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) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeByStringTest {static String _STR = "Java中文社群";public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(SwitchOptimizeByStringTest.class.getSimpleName()) // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic 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;}// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行blackhole.consume(s1);}@Benchmarkpublic 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";}// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行blackhole.consume(s1);}
}

特殊说明:本文使用的是 Oracle 官方提供的性能测试工具 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)进行测试的。

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


从 Score 列(平均完成时间)可以看出 switch 的性能依旧比 if 的性能要高。

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

switch 性能优化

我们知道在 JDK 1.7 之前 switch 是不支持 String 的,实际上 switch 只支持 int 类型

在 JDK 1.7 中的 String 类型,其实在编译的时候会使用 hashCode 来作为 switch 的实际值,以上 switch 判断字符串的代码,编译为字节码实际结果如下:

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;}}// 忽略其他代码...
}

知道了 switch 实现的本质,那么优化就变得比较简单了。

从以上的字节码可以看出,如果要优化 switch 只需要把 String 类型变成 int 类型就可以了,这样就剩了每个 case 中进行 if 判断的性能消耗,最终的优化代码如下:

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

此时我们使用 JMH 进行实际的测试,测试代码如下:

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) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeByStringTest {static String _STR = "Java中文社群";public static void main(String[] args) throws RunnerException {// 启动基准测试Options opt = new OptionsBuilder().include(SwitchOptimizeByStringTest.class.getSimpleName()) // 要导入的测试类.build();new Runner(opt).run(); // 执行测试}@Benchmarkpublic 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;}// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行blackhole.consume(s1);}@Benchmarkpublic 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;}// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行blackhole.consume(s1);}@Benchmarkpublic 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";}// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行blackhole.consume(s1);}
}

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


从以上结果可以看出,String 类型的 switch 判断,经过优化之后,性能提升了 2.4 倍,可谓效果显著。

注意事项

以上的 switch 优化是基于 String 类型的,同时我们需要注意 hashCode 重复的问题,例如对于字符串“Aa”和“BB”来说,他们的 hashCode 都是 2112,因此在优化是需要注意此类问题,也就是说我们使用 hashCode 时,必须保证判断添加的值是已知的,并且最好不要出现 hashCode 重复的问题,如果出现此类问题,我们的解决方案是在 case 中进行判断并赋值。

其他优化手段

我们本文重点讨论的是 switch 性能优化的方案,当然如果处于性能考虑,我们还可以使用更加高效的替代方案,例如集合或者是枚举,详见我的另一篇文章《9个小技巧让你的 if else看起来更优雅》。

总结

通过本文我们知道 switch 本质上只支持 int 类型的条件判断,即使是 JDK 1.7 中的 String 类型,最终编译的时候还是会被转化为 hashCode(int)进行判断。但因为编译成字节码后会在 case 中使用 if equals 进行比较,所以性能并不算太高(只比 if 高一点点),因此我们可以直接把 String 转化成 int 类型进行比较,从而避免在 case 中进行 if equals 判断的性能消耗,这样就大大的提升 switch 的性能,但需要注意的是,有些 key 值的 hashCode 是相同的,因此在优化时需要提前规避。

最后的话

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

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

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

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



推荐阅读
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • Go语言实现Redis客户端与服务器的交互机制深入解析
    在前文对Godis v1.0版本的基础功能进行了详细介绍后,本文将重点探讨如何实现客户端与服务器之间的交互机制。通过具体代码实现,使客户端与服务器能够顺利通信,赋予项目实际运行的能力。本文将详细解析Go语言在实现这一过程中的关键技术和实现细节,帮助读者深入了解Redis客户端与服务器的交互原理。 ... [详细]
  • 计算 n 叉树中各节点子树的叶节点数量分析 ... [详细]
  • 状态模式在软件设计中的应用与实现
    本文以酒店管理系统为例,探讨了状态模式在软件设计中的应用与实现。酒店房间的状态包括空闲、已预订和已入住,这些状态之间可以相互转换。通过引入状态模式,系统能够更加灵活地管理和响应不同状态下的操作,提高了代码的可维护性和扩展性。此外,状态模式还简化了状态转换的逻辑处理,使得系统的整体架构更为清晰和高效。 ... [详细]
  • 使用 MyEclipse 和 TestNG 测试框架在 Java 中高效进行单元测试
    通过MyEclipse集成TestNG测试框架,可以在Java开发中高效地进行单元测试。本文介绍了在JDK 1.8.0_121和MyEclipse 10.0离线环境下配置和使用TestNG的具体步骤,帮助开发者提高测试效率和代码质量。 ... [详细]
  • 开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用
    开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用 ... [详细]
  • Java 中优先级队列的轮询方法详解与应用 ... [详细]
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • 本文介绍了一种基于最大匹配算法的简易分词程序的设计与实现。该程序通过引入哈希集合存储词典,利用前向最大匹配方法对输入文本进行高效分词处理,具有较高的准确率和较快的处理速度,适用于中文文本的快速分词需求。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 如何在 Java LinkedHashMap 中高效地提取首个或末尾的键值对? ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • Java 9 中 SafeVarargs 注释的使用与示例解析 ... [详细]
  • 深入解析Java中HashCode的功能与应用
    本文深入探讨了Java中HashCode的功能与应用。在Java中,HashCode主要用于提高哈希表(如HashMap、HashSet)的性能,通过快速定位对象存储位置,减少碰撞概率。文章详细解析了HashCode的生成机制及其在集合框架中的作用,帮助开发者更好地理解和优化代码。此外,还介绍了如何自定义HashCode方法以满足特定需求,并讨论了常见的实现误区和最佳实践。 ... [详细]
author-avatar
tttrj
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有