记一次程序优化历程

用java写了一个游戏计算最高得分的工具,游戏规则是,一个厨师能够带一个厨具,作三种菜谱,而后能够上场三个厨师。厨师和厨具分别有技能,技能有许多种效果,有些特殊的还能影响其余厨师,菜谱有技法要求限制。计算就是用3个厨师,3个厨具,9个菜谱,计算得分。java

厨师有60多个,菜谱200多,厨具几十个。
首先想到了动态规划,可是每一个菜谱须要消耗必定数量食材,每种食材都有数量限制,前一个菜谱的选择会影响后边的结果,厨师,厨具,菜谱能够作的数量,维度太多,搜索空间太大,没想出思路。算法

最后想到的思路是,生成 9个菜谱的有序组合(安菜谱价格由高到低,生成),3个厨师的无序组合,三个厨具的有序组合。
菜谱组合数41989
厨师组合数3095
厨具组合数79079数组

而后先组合厨师和菜谱,保留得分最高的3k个,而后再组合厨具,从新计算得分。我只使用前1000个菜谱组合,在使用i7-6700HK的cpu上计算时间也要十几分钟。oracle

最开始觉得是算法写的烂,三个维度数据量太大。而后了解到idea的profiler工具能够用于性能分析。ide

分析结果结果如图
image.png函数

能够从图中看到,对一些函数的cpu使用状况有一个统计,左侧是函数的cpu使用占比。可是统计的不全,应该是一些非函数调用的语句没有统计。工具

最开始一个线程,30个厨师组合,1000个有序菜谱组合,一次计算耗时是50s左右,使用profiler分析cpu耗时,发现是计算厨师技能的函数耗时占比30%
image.png性能

这个函数是计算三个厨师的技法加成,以及技法对其余厨师的影响,大量的加法运算,致使耗时长。其实这个函数只和厨师组合有关,因此厨师组合肯定,技法效果也应该肯定了,可是个人每一组厨师和每一组菜谱(有序组合变无序为1680个)的搭配都要调用该方法,也就是重复计算了1680次。在没使用profiler前,还真不知道大部分耗时在这里。测试

优化后耗时变为30s,优化

而后继续测试, 发现此次ArrayList的subList方法和get方法耗时占大头,
由于我将长度为9的菜谱组合放在ArrayList中,如今要拆成3个3等分,因此使用了subList,最后改成使用二维数组装三等分的菜谱。

优化后耗时变为21s左右。

说到这里有个很神奇的现象,个人性能测试在 window10下, 分别使用openjdk8,oraclejdk11,oraclejdk15,GraalVM CE 20.2.0,下测试, 除了GraalVM CE 20.2.0,其余的都稳定在20s左右,惟独GraalVM,最后能优化到14s左右,而我发现这个优化会和一个for循环有关,

14秒耗时版本
image.png

20秒耗时版本
image.png

不出意外,我不懂汇编,没有自大到分析汇编后的代码,可是感受和Link有关,因而把牵涉大量计算部分的LinK都改成了对象数组。

结果执行时间减小到7s左右。 当我兴奋的使用openjdk8,oraclejdk11,oraclejdk15测试的时候,发现没有变化,仍是20秒左右,只有GraalVM稳定在7秒。

mmp...