目录
前言:
本文第一步是分析影响效率的代码,第二步是代码优化。这次把程序优化后,运行时间1分钟以内哦!
用户反馈某个程序运行缓慢,需要优化一下效率,现在的情况是该程序运行一个公司代码的数据,运行时长高达四五个小时,严重影响业务部门月结出报表的效率。
其实接到这个任务我还是有点慌张的,因为这个程序已经被上一任开发优化过一次了,一年半前,该程序运行四五个小时,优化后快达十五分钟!!但是因为这个报表的运行逻辑是需要抓取从上线初到现在的数据,时间跨度长达十多年,尽管之前优化过一次,但是随着业务越做越大,单据越来越多,数据量也越来越大,所以程序现在又慢下来了,任重而道远!
然后呢~因为这是一个单纯的取数报表,也不会调用一些函数什么的,所以这次的案例记录就没有异步调用这样子的优化。拿起代码一看其实就知道优化的点在哪里,一是READ使用线性遍历;二是LOOP+LOOP WHERE嵌套使用(这个也是线性遍历),但是为了严谨一点,我们使用SAP标准工具分析一下代码,记录如下:
一、ST12
1、测试效率
测试代码效率的事务代码有很多,比如SAT、SM55这些代码,然后我使用了下来感觉ST12是最好用的,非常清晰,下面展示一下ST12的使用方法。
如下填写使用者账号、程序名称后,点击执行,会直接跳转到程序的选择屏幕界面,这里面也有填写事务代码的位置,但是有时候我填写事务代码会执行失败,填写程序名称就是完全没问题的(如果该程序没有选择屏幕就会直接运行)
填写好选择屏幕的字段之后,直接执行程序就可以,程序执行完之后点击返回,就可以看到程序的运行时间已经记录到ST12的界面了。
刚返回这个界面的时候,会有一个红色的饼图,那个没关系,直接点击ABAP trace进行分析界面就可以了。
2、分析代码效率
分析界面长这个样子
ABAP:2805432450表示该程序执行ABAP语句使用了2805432.450毫秒,即2805.432450秒,即46分钟左右(可能有小伙伴要问,这个和Lily开篇说的四五个小时也差得太远了吧~好吧其实是我专门找的票据数量少的公司以及去年期间来跑的,确实不敢跑今年的数据,要是跑今年的数据,我恐怕一整天都没办法做工作啦)。
我们继续看下面这个截图。左边是子例程或者一些语句,右边是时间,GROSS是表示纳秒,我们只需要看program是我们分析程序名的对应的子例程(或语句)就好了,可以看到子例程PERFORM GET_ALLDATA占用了84.3%的时间,双击可以进入代码,包括语句等都可以导航到代码对应的位置,所以很精确地定位是哪一块儿代码需要优化。
3、具体代码
双击左边我们可以直接进入程序查看代码
我们发现子例程GET_POSTDATA是包含在子例程GET_ALLDATA里面的
一层一层查看,最后发现是这几个LOOP导致子例程变慢,我们进入程序看具体逻辑。
3.1、第一处代码
在LOOP LT_ALVITEMS的时候,嵌套使用了LOOP WHERE,内层循环LOOP WHERE也是线性遍历,每次内层循环都需要遍历 LT_CKMLCR,即使使用了 WHERE 条件,仍然需要逐条检查。LT_ALVITEMS 有5W条数据,LT_CKMLCR有6.8W条数据,时间复杂度为O(3.4×10^9)这意味着在最坏情况下(最坏情况,即符合条件的数据每次都在最后才能找到),代码需要执行 34 亿次操作。尽管已经使用了指针,但整个LOOP…ENDLOOP,花费了15分钟左右,占用了程序三分之一的时间。
(ST12界面下面的LOOP LT_OLD也是类似的情况,LOOP嵌套,我们这里不重复分析了)
3.2、第二处代码
在READ的时候使用线性查找,时间复杂度为 O(n),n 为 LT_CKMLHD 的记录数(26 万)。因此,总时间复杂度为 O(6万 * 26万) = O(1.56 * 10^10)(好吧,我看了一下对于46分钟来说,23秒LOOP 6W*READ 26W数据,还是挺快的了,苦笑)
二、修改代码
1、LOOP嵌套优化
1.1、举例代码
LOOP+LOOP WHERE的优化点是将LOOP WHERE 改为二分法查找数据,将内层loop的内表设置为非主键排序表。
1.2、语法示例
定义内表语法为:
表 LIKE TABLE OF 结构 WITH NON-UNIQUE SORTED KEY 非主键名字 COMPONENTS 字段1 字段2.
嵌套循环的语法为:
LOOP AT 内表 INTO 工作区 USING KEY 非主键名字 WHERE 字段1 的条件 AND 字段2的条件。
这里不止可以使用工作区,还可以使用指针,是与以前LOOP语法一样的,只是需要使用USING KEY
1.3、注意事项
1.3.1、非主键排序表是由系统格外分布内存进行存储的,不需要我们再对其进行SORT排序操作;
1.3.2、因为排序表的实质还是使用二分法查找,所以不能进行倒序搜索,会出错!这点需要特别注意。我的代码里面是需要按照最新时间取物料价格,所以需要按照倒序排序,这里我将符合条件的数据提取出来重新生成了一个小的副表LT_CKMLCR1,再对其进行条件筛选,效率还是比较理想的;
1.3.3、如果该排序表需要继续使用BINARY SEARCH,而且搜索条件不是我们定义的非主键,这时还是需要对其进行排序的,并且是可行的。
2、READ 线性优化
2.1、举例代码
READ TABLE的优化点就是将线性遍历改为二分法查找。不需要对内表进行结构上的调整,只需要在读表之前将其进行排序。
2.2、语法如下
排序语法:
SORT 内表 BY 字段1 字段2 字段3.
读表语法:
READ 内表 …… WITH KEY 字段1条件 字段2条件 BINARY SEARCH.
2.3、注意事项
2.3.1、参与二分法查找的条件字段必须全部都进行排序,并且这些字段里面不可以使用倒序排序,正序排序为ASCENDING,倒序排序为DESCENDING。系统默认排序为正序排序;
2.3.2、在排序时字段顺序必须与READ时顺序一致。例如SORT按照字段1、字段2、字段3进行排序,那么在READ时也必须按照字段1、字段2、字段3进行条件筛选,不允许按照字段1、字段3、字段2的顺序进行条件筛选;
2.3.3、参与排序的字段可以多,不可以少。假如内表需要BINARY SEARCH两次,第一次筛选有字段1,字段2;第二次筛选有字段1,字段2,字段3。那么只需要将字段1、2、3排序一次就可以了,对于第一次BINARY SEARCH来说,字段1,字段2 始终是已经排过序的,没有影响;
2.3.4、在实际应用中,对于不参与筛选条件的字段,倒序排序对二分法结果没有影响。因为使用READ语法读取数据,是要确保系统中有<=1条数据的情况,才能取出符合条件的数据,假如数据有在该条件下符合重复的数据,那么其实取出来的数据是不准确的,几乎不会出现这样的情况,因为多条的情况下需要使用LOOP了;(当然!有时候只需要判断该条件下是否存在数据,那这时候具体数据已经不重要了,其余的数据对于倒序还是正序也就不重要了)(这里要纠正一下,如果有重复的数据,会取第一条,我忘记之前我是怎么做的测试了,但是最近遇到的情况是确实是取的第一条数据,而且没有出错,不存在偶然性情况,后面有空了我再重新测试一下)
2.3.5、内表排序应该写在LOOP循环外面,不然在LOOP循环时,每经过一次SORT都要进行一次排序,当内表数据量大的时候,还是很花费时间的;
三、疑惑点分析
1、疑惑点1
为什么同样是线性查找,LOOP +READ时间复杂度为O(1.56*10^10 ),LOOP+LOOP WHERE 时间复杂度为O(3.4*10^9),看似LOOP+READ时间复杂度高,但是实际运行中,LOOP +READ只花费了20秒左右,而LOOP+LOOP WHERE 花费了15分钟,运行效率为什么差这么多呢?
(这是我使用Deepseek分析得到的)这种差异主要由以下因素导致(假设内表数据条数为M):
1.1、数据访问模式
LOOP + READ:
READ TABLE 是单次查找操作,每次查找都会从表的开头开始遍历,直到找到匹配的记录。
如果 LT_CKMLHD 表的记录分布较为均匀,且匹配记录位于表的前半部分,则实际查找次数可能远小于 M。
此外,READ TABLE 是 ABAP 的内置操作,可能经过高度优化,运行效率较高。
LOOP + LOOP WHERE:
LOOP WHERE 是嵌套循环,每次外层循环都会触发一次完整的内层循环。
即使使用 WHERE 条件,内层循环仍然需要遍历所有匹配的记录,直到找到满足条件的记录。
如果 LT_CKMLCR 表的记录分布不均匀,或者匹配记录位于表的末尾,则实际查找次数可能接近 M
1.2、 数据分布
如果 LT_CKMLHD 表中的记录分布较为均匀,且匹配记录通常位于表的前半部分,则 READ TABLE 的实际查找次数会显著减少。
如果 LT_CKMLCR 表中的记录分布不均匀,或者匹配记录通常位于表的末尾,则 LOOP WHERE 的实际查找次数会接近理论值。
1.3、 缓存和内存访问
LOOP + READ:
READ TABLE 是单次查找操作,可能更容易利用 CPU 缓存,减少内存访问的开销。
如果 LT_CKMLHD 表较小,可能完全加载到缓存中,进一步加快查找速度。
LOOP + LOOP WHERE:
嵌套循环会导致频繁的内存访问,尤其是当 LT_CKMLCR 表较大时,缓存命中率较低,内存访问开销较大。
1.4、 退出条件
LOOP + READ:
一旦找到匹配记录,READ TABLE 就会立即返回,无需继续遍历。
LOOP + LOOP WHERE:
即使找到匹配记录,内层循环仍然需要遍历所有匹配的记录,直到满足退出条件(如 EXIT)。
1.5、ABAP 运行时优化
ABAP 运行时可能对 READ TABLE 进行了特殊优化,例如使用更高效的查找算法或缓存机制。
LOOP WHERE 的优化可能较少,尤其是在嵌套循环的情况下。
2、疑惑点2
忘记了。。
四、总结
累了,这次没有总结,总结就是看注意事项…^_^
最后,将程序里面所有嵌套循环、线性READ都修改后,再次运行ST12,统计一下新程序的运行相同数据所需要的时间
同样的数据,优化后时间为34411毫秒,约34秒,效率提升约98.78%,四舍五入是99%,再四舍五入是100%,嗯~很不错嘛!
五、说明
很有必要说明一下!我这篇文章(应该说是程序优化的思路与工作记录)是参考的一位大佬的文章写的,很感谢SAP业内大佬对于知识技能的无私分享!这是大佬文章的链接:
对ABAP程序调优的学习(四)LOOP WHERE KEY_abap loop where-优快云博客
也希望我的这篇文章能够对需要的人有所帮助~