认真工作中.jpg
简述性能分析与调优
czfshine
程序的运行总要消耗系统资源的,无论是CPU,内存还是磁盘IO。我们编写高质量的代码除了易用性可扩展性之外,还应注重资源的使用情况。
性能分析可以分析你的代码在用例下各部分所消耗的资源情况,而性能调优是对分析结果进行解析,并有方向的针对代码进行优化。
本文使用我最近在弄的一个聊天记录分析程序进行示例,简单说下怎么进行性能分析和调优。
怎样进行性能分析与调优
先讲下背景,这个项目分析聊天记录的数据库,解析生成Java对象。代码使用Java和Kotlin
,性能分析软件使用JProfiler,安装过程我就省略了,不是本文的重点。
下面开始讲怎么进行性能分析。
首先是编写测试用例,和TDD(测试驱动开发)里面的测试用例不一样的是,我们这个用例主要针对你想进行性能分析的代码,保证该部分代码的常用情况都包含在内,而且测试数据量得完整一些。
这里我测试对所有数据进行读入的部分代码。
编写测试用例后点击IDEA的Profile按钮开始进行性能分析。
这里的性能分析分为两个方向,一是对CPU使用情况进行分析,看哪些方法最耗时,并对其进行优化。二是分析内存使用情况,看有没有内存泄漏。
性能分析的原理大概是利用反射(或类似机制)给各个方法增加统计代码,并收集统计信息。显然这样做会影响性能。JProfiler默认提供两个选项,一个是使用全部的特性对代码进行分析,可以很详细得出各个对象与方法的使用情况,这种方法对性能影响最大。第二个选项是只对CPU进行简单分析,这种方法得到的数据较少,不过同样对性能影响较小。
我们这里先选用第二选项。
之后可以对我们需要统计的细节进行配置,网上教程都有,就不赘述了。
之后将会运行测试代码,并收集信息。我们可以在JProfiler的监控面板看到内存,cpu的使用情况。
等代码运行结束点击工具栏的按钮让它停止记录。
CPU性能分析
看Hot Spots 可以看到一个方法的总调用时间与调用次数。
我们先分析一下前几个的性能热点,
第一个read是在等待用户输入,所以时间长,不用理。
第二个是将字符串中的特殊符号(XML)进行转义的,下面会分析。
第三和第五是数据库的,大部分时间应该是在文件IO和安全性上面,现在先不管。
第四的contains下面进行分析。
第六个listfiles是文件IO的,慢是正常。
第七个,代码里大量使用哈希表,所以hash函数调用频次多,占用资源大也正常。
接下来的方法占用时间太少就不分析了。
在左侧面板可以点击Call Tree 查看调用树。
调用树显示各个方法的调用层次与运行时间,我们主要分析占用大多数时间或运行时间异常的方法。
看上面那个,Talker.addMessage是将一条聊天记录加到对应的发送者内部的HashSet里面的,理论上复杂度应该是O(1)的,不应该占用那么多的时间。看他调用的子方法,大部分时间花在contains 上面了,这个方法是O(n)的,所以问题出在这。
先分析原因:
因为这个项目支持从多数据库导入聊天记录,所以这里使用contains是为了防止重复消息的。
再看看能不能解决:
其实本项目使用单例工厂模式,对每一条相同的消息,都只构造一个实例。而且这个Talker.addMessage方法是在Message的工厂函数内被调用的,这样已经可以保证一条消息只会被加入一次,所以这里的contains是多余的,分支不会被执行。
验证一下:
使用全部数据(多个数据库)执行,可以发现println 语句并不会被执行,也就是说上面的分析是正确的。
解决方法:
删掉if语句,这样可以节省近5%的时间(其余4%是计算对象Hash的时间,无法消除的).
结果如下:
第二个是XML转义的
使用这个函数的原因的要将聊天记录导出成docx文件,本质上就是输出成xml,所以要对特殊字符进行转义。不过代码中的转义是写在基类里面的,其实并不需要对所有消息的内容进行转义,那些图片,表情的消息就不需要,只需对文本消息转义即可。而且,文本消息的内容长度所占的比重不到1%。
CPU分析的就到这,内存分析的请听下回分解。
干货多多,
关注科联自科部
领取专属 10元无门槛券
私享最新 技术干货