性能优化之代码的指令层优化

前两天发了一个关于三目运算符优化的动态,以code review的形式和大家交流,后来发现大家交流的很热烈,各抒己见,很nice,有沟通才有碰撞,有碰撞才有深刻的理解和进步。本文给出优化的原因和方法,感兴趣的可以看下。

讨论的内容如下,即下面划线部分的代码可以进行优化,理由是:已装箱的值被拆箱,然后又立即重新装箱!这对性能有影响
在这里插入图片描述
大家的讨论主要集中在两点:

  1. 怎么优化
  2. 没必要,纯属工作不饱和或者瞎折腾

针对第一点,今天本文会给出详细的答案;针对第二点,大家说的也没错,这种优化,你不管系统基本照样运行,但是,作为程序员,拿追剧、刷视频的时间来折腾一些技术,我是很喜欢的,喜欢和这种细节battle,不弄明白吃饭也不香。很多时候,我们要具有瞎折腾的勇气和好奇心,尤其是在当下这种浮躁的环境下;当然,这都是本文的题外话,扯远了。

OK,我们言归正传。为什么三目运算符中包装类型和基本类型共存时会影响性能呢?比如:

Integer brandId = Objects.isNull(content.getBrandId()) ? 0 : content.getBrandId();

首先影响性能是肯定的(性能优化不是银弹,高并发场景下我们需要方方面面的优化)。要知道上述代码为什么会影响性能就要从java代码的执行原理来说了,大家应该都知道我们写的.java文件要想被执行,需要经过加载->链接->初始化,然后JVM才会执行对应的代码,JVM拿到class文件即字节码文件去执行对应的指令。如下图:
在这里插入图片描述

所以对于上图中的代码,即如下代码:

for (StrategyContentDO content : saveRequestDTO.getStrategyContentList()) {
            Integer categoryId = content.getCategoryId();
            Integer brandId = Objects.isNull(content.getBrandId()) ? 0 : content.getBrandId();
            doCheck4Busi(uniqueChecker, content, categoryId, brandId);
}

我们需要拿到它的字节码对应的JVM执行指令,一看便知。你可以通过javac命令编译Java文件为字节码文件,即javac xxx.java,因为字节码文件我们人类看不懂,所以你可以再用javap命令将字节码文件反汇编为JVM的执行指令,这个执行指令我们是可以阅读的,即javap -c xxx。上述获取执行指令的过程觉得麻烦,这里推荐idea中的一个插件:jclasslib Bytecode Viewer(文末有使用说明,很简单很方便)。这里我贴出相关代码的执行指令,我们一起看下:

没有优化时的代码对应的JVM执行指令:

......省略一些无关指令
190 aload 6
192 invokevirtual #25 <com/demo/StrategyContentDO.getCategoryId : ()Ljava/lang/Integer;>
195 astore 7
197 aload 6
199 invokevirtual #26 <com/demo/StrategyContentDO.getBrandId : ()Ljava/lang/Integer;>
202 invokestatic #27 <java/util/Objects.isNull : (Ljava/lang/Object;)Z>
205 ifeq 212 (+7)
208 iconst_0
209 goto 220 (+11)
212 aload 6
214 invokevirtual #26 <com/demo/StrategyContentDO.getBrandId : ()Ljava/lang/Integer;>
217 invokevirtual #28 <java/lang/Integer.intValue : ()I>
220 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
223 astore 8
225 aload 4
227 aload 6
229 aload 7
231 aload 8
233 invokestatic #29 <com/demo/Foo.doCheck4Busi : (Ljava/util/Set;Lcom/demo/StrategyContentDO;Ljava/lang/Integer;Ljava/lang/Integer;)V>
236 goto 168 (-68)
239 return

从上述指令集中可以清晰的看到代码的执行过程:
在这里插入图片描述
从代码的运行指令中我们看到,这段代码:

Integer brandId = Objects.isNull(content.getBrandId()) ? 0 : content.getBrandId();

当字段brandId等于null时,基本类型0需要装箱,不为null时,代码的执行要先拆箱再装箱。

那怎么优化呢?也很简单,我们保持三目运算符中字段类型一致即可(避免频繁装拆箱),代码处理如下:

//缓存的Integer常量
public static final Integer INTEGER_ZERO = 0;

在这里插入图片描述

优化后Java代码对应的JVM执行指令集:

......省略一些无关的指令
190 aload 6
192 invokevirtual #25 <com/demo/StrategyContentDO.getCategoryId : ()Ljava/lang/Integer;>
195 astore 7
197 aload 6
199 invokevirtual #26 <com/demo/StrategyContentDO.getBrandId : ()Ljava/lang/Integer;>
202 invokestatic #27 <java/util/Objects.isNull : (Ljava/lang/Object;)Z>
205 ifeq 214 (+9)
208 getstatic #28 <com/demo/Foo.INTEGER_ZERO : Ljava/lang/Integer;>
211 goto 219 (+8)
214 aload 6
216 invokevirtual #26 <com/demo/StrategyContentDO.getBrandId : ()Ljava/lang/Integer;>
219 astore 8
221 aload 4
223 aload 6
225 aload 7
227 aload 8
229 invokestatic #29 <com/demo/Foo.doCheck4Busi : (Ljava/util/Set;Lcom/demo/StrategyContentDO;Ljava/lang/Integer;Ljava/lang/Integer;)V>
232 goto 168 (-64)
235 return

在这里插入图片描述

从优化后的指令中可以看出,三目运算符没有装拆箱的指令了,减少了JVM要执行的指令数,也就减小了系统的执行时间,这实际上就是所谓的性能优化中的指令层优化

后记

性能优化不是银弹,这世界上不存在一种万能的数据结构去高性能的存、取任何类型的数据,也不存在一种万能的性能优化方法去优化所有的性能问题。缓存、接口内部处理并发化,外部调用异步化、合理的数据结构、合理的代码处理(预热、空间换时间等,而不是全部甩给JVM)等等等等,都是影响性能的因素。指令级别的优化实际上到处都有,如Spring框架、Mybatis框架等等,有时候只是我们不了解,所以看到了也不认识,也就不知道了。所以有时候刷剧的时间偶尔了解些”无用的东西“,对自己的技术之路或许会有意想不到的收获。
OK,咱们回聊~

jclasslib Bytecode Viewer插件使用说明:

1.安装插件
在这里插入图片描述
2.rebuild项目
在这里插入图片描述
3.选中java文件,生成指令集
在这里插入图片描述
4.查看指令集
在这里插入图片描述
自己可以多尝试,多看,多理解慢慢就会了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值