提高Java开发质量之性能瓶颈
---Quest JProbe实践之四
应用的性能瓶颈很大程度上取决于程序中算法的好坏,算法好则应用的反应时间比较快,耗费的系统资源也比较少;反之,应用反应迟缓,效率低下。我们如何发现应用使用的算法好坏和诊断出应用存在的性能瓶颈呢?下面我们使用JProbe Profiler对给出的两个用例进行诊断分析,找出其性能瓶颈。
性能瓶颈可定位到类中的方法,一般有两种原因,一个是单次执行该方法的时间长,另一个原因是该方法被调用的次数多。
识别性能瓶颈-例1
1. 我们首先记录测试用例的过程并搜集数据,如下图所示,是JProbe Profiler 的Call Graph窗口。我们注意到那些在图右边的节点颜色比较亮,那是因为该节点所含有的累积时间包括了其子节点和子树的时间,所以后面的节点颜色一般越来越暗。
2. 下面识别程序执行的路径(critical path)。在这个用例中,我们知道应用执行的主路径是从调用GameController.doGet方法开始的。从GameImageServlet.doGet方法开始的相应支线实际上是由主要调用树的方法初始化。GameImageServlet.doGet支线主要负责画图传给浏览器,所以我们集中观察该支线。(后面我们通过运行改进后的算法即第二个算法发现改进后的算法不再需要执行该支线,这就节约了执行这段支线所花的时间)
3. 识别出路径后就要把它分离出来,选中GameController.doGet并点击Isolate Subtree。
4. 在Call Graph下面,点击Shown标签可以显示Call Graph中节点代表的方法内容。
5. 从刚才分离出来的支线可以看到,最左边的那个节点最亮,顺着这个节点从左到右看过去,直到支线的末尾。在这个用例中,最后的节点是MediaTracker.waitForID( )和Toolkit.getDefaulToolkit( )。你可能也注意到在一些节点上有个小锁头,这个锁头表示这个方法上面的数据被封装了,意味着我们不必要关注这些数据。
6. 选中MediaTracker.waitForID( )方法节点,在下面的方法列表中,我们可以看到执行该方法共花了655ms和该方法被调用了200次。同样选中Toolkit.getDefaulToolkit方法,执行该方法花了722ms和该方法也被调用了200次。这两个方法花费的时间都比较多,性能瓶颈可能就由于它们引起的。
7. 由于这两个方法是从java.awt框架中来的,所以不能修改这些方法的代码;我们能做的只能是以另一种形式调用它们或者不调用它们,这时需要找到哪个方法初始化并调用了这两个方法。
8. 顺着调用树找到了GameImageManager.loadImage,接着顺着GameImageManager.loadImage再往回找MineSweeperRenderer.renderMineMapImage_MethodTime。这就是开始调用java.awt中的MediaTracker.waitForID( )和Toolkit.getDefaulToolkit( )方法。这时我们可以得出结论:性能瓶颈是由于用例包中的renderMineMapImage_MethodTime方法为游戏版的每个小方块调用了耗资源的第三方方法。
识别性能瓶颈-例2
这个简单的用例里面只含有一个问题算法,接下来我们分析一下这个方法的嵌套循环算法。打开刚录制snapshot的Call Graph窗口查看用例的方法调用图,在调用图中我们看到Polynomian.evaluate方法是以比较鲜亮的颜色显示,说明其执行时间比较多,选中该方法。看下面的方法列表,可以注意到它只被调用了1次,但共耗费了1163ms执行,这说明它是一个有问题的方法,执行的时间太多,这可能就是性能瓶颈所在。
运行改进后的用例代码
我们假设改进后的算法即第二种算法执行时间比第一种算法的执行所花时间少,计算快。我们再来看看Polynomial.evaluate方法使用改进后的算法其执行时间是否减少了。选中Polynomial.evaluate方法节点,我们注意到它的Cumulative Time明显减少了,只使用了12ms。
测量性能改进情况
从以上两个步骤,可以看出第二种算法明显执行的快,下面我们比较一下它们,看性能方面究竟改进了多少。选中第二个snapshot,点击Snapshot Difference,然后再选中录制的第一个snapshot文件并单击OK。这时打开了Snapshot Difference窗口,上面显示了类的前后两次试验存在的不同情况。
选中Polynomial.evaluate方法,可以注意到前后两次调用次数没有变,但这个方法的Cumulative Time明显减少,将近99%,图上面的负号表示性能得到了改善。因此,执行Horner法则的算法比第一个嵌套算法快99%。
这个用例说明了算法的好坏直接影响了应用的性能。