美文网首页
性能优化之代码的指令层优化

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

作者: Running的程序员 | 来源:发表于2022-10-23 20:22 被阅读0次

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

    讨论的内容如下,即下面划线部分的代码可以进行优化,理由是:已装箱的值被拆箱,然后又立即重新装箱!这对性能有影响

    建议优化类型不一致的三目运算符.png

    大家的讨论主要集中在两点:

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

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

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

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

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


    简要流程.png

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

    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
    
    从上述指令集中可以清晰的看到代码的执行过程: JVM执行指令的流程.png

    从代码的运行指令中我们看到,这段代码:

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

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

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

    //缓存的Integer常量
    public static final Integer INTEGER_ZERO = 0;
    
    三目运算符中保持类型一致.png

    优化后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
    
    指令中没有装箱拆箱指令了.png

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

    后记

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

    jclasslib Bytecode Viewer插件使用说明:

    1.安装插件 008650e0222a3151476f6d84e63b7b8.png 2.rebuild项目 e52d58a0033ad82522f84a15454649f.png 3.选中java文件,生成指令集 f7d91ebb41e150d220bef810d67f994.png 4.查看指令集 e0ed197789ecb8436ef5ae13a38d39a.png

    自己可以多尝试,多看,多理解慢慢就会了

    相关文章

      网友评论

          本文标题:性能优化之代码的指令层优化

          本文链接:https://www.haomeiwen.com/subject/cfigzrtx.html