【业务案例】震惊!ABAP程序优化效率提升99%,这个人到底做了什么?!

目录

前言:

一、ST12

二、修改代码

1、LOOP嵌套优化

2、READ 线性优化

三、疑惑点分析

四、总结

五、说明


前言

本文第一步是分析影响效率的代码,第二步是代码优化。这次把程序优化后,运行时间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-优快云博客

      也希望我的这篇文章能够对需要的人有所帮助~

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值