ABAP效率优化 LOOP循环嵌套效率分析

前言

        最近优化了一部分程序,基本都是嵌套循环导致的效率低下;ABAP开发时会尽量避免使用嵌套循环,但是实际应用场景绕不开嵌套循环;针对嵌套循环优化,比较常见的优化方式为二分法read+loop index处理,这里通过一个demp程序更好的分析下嵌套循环相关的效率。

实例场景(最后一列为耗时微秒)

        

ABAP效率优化 LOOP循环嵌套效率分析_第1张图片

        1. 针对嵌套循环使用使用二分法read+loop index处理效率优化方面最优,胜于使用排序表

        2. loop index 做条件筛选时占用时间极少,几乎可以忽略不计

        3. 针对内表读取二分法read效率最佳,尽管排序表使用key做关联,默认使用二分法

        4. read 遍历效率远优于loop where 遍历效率

        ABAP效率优化 LOOP循环嵌套效率分析_第2张图片

        1. read table binary search 随着数据表条目的增加,增加的时间比loop using key更多,但是如下1000000条数据结果,仍然有差距。

        2. 使用hash表 primary key read效率比二分法更优(hash表primary key主键必须唯一,read table 时采用hash算法,因此不适用循环嵌套)

ABAP效率优化 LOOP循环嵌套效率分析_第3张图片

        其它demo输出数据

ABAP效率优化 LOOP循环嵌套效率分析_第4张图片

最终结论

        1. 循环中使用read时,尽量使用二分法,且尽量loop小表,read大表

        2. loop 嵌套循环时,外层循环放小表效率更优

        3. 如果主键确定的read可以考虑使用hash表优化效率

        4. loop where 遍历效率较低,带where条件遍历仅为无where条件遍历时的一半,这是嵌套循环耗费时间的原因之一。

        实际处理嵌套循环时遵循使用效率更高的语句处理大表,较低的处理小表原则

        效率排序

                loop 无条件

                read <  sorted table read < binary search < hash primary key

        1. 由于系统差异,测试结果存在有一定差异,实践出真知。

        2. hash表sorted key 效果和排序表一致,hash key需要唯一主键,没有计入测试。

       https://mp.csdn.net/mp_blog/creation/editor/125475812

        Demo程序

*&------------------------------------------------------------------*
*& Report ZDEOMO
*&------------------------------------------------------------------*
*&Author : Fireworks                                                *
*&Description : 嵌套循环场景效率测试                                *
*&------------------------------------------------------------------*
REPORT zdemo.

*&---------------------------------------------------------------------*
* DATA Declare
*&---------------------------------------------------------------------*
TYPES: BEGIN OF ty_alv_out,
         test_scenario TYPE text50,
         mode          TYPE i,
         loop_index    TYPE sy-index,
         timestampb    TYPE tzntstmpll,
         timestampe    TYPE tzntstmpll,
         spendtime     TYPE i,
       END OF ty_alv_out.

TYPES: BEGIN OF ty_subroutines,
         form          TYPE char30,
         test_scenario TYPE text50,
         mode          TYPE i,
       END OF ty_subroutines.

TYPES: BEGIN OF ty_test_itab,
         key_char TYPE char2,
         key_num  TYPE numc3,
         int      TYPE sy-index,
         field_1  TYPE text30,
       END OF ty_test_itab.
TYPES: tt_alv_out  TYPE TABLE OF ty_alv_out.

DATA: gt_itab_01     TYPE TABLE OF ty_test_itab,
      gt_itab_02     TYPE TABLE OF ty_test_itab,
      gt_subroutines TYPE TABLE OF ty_subroutines.

DATA gv_mode TYPE i.

DATA: gt_alv_out          TYPE tt_alv_out,
      go_salv             TYPE REF TO cl_salv_table,
      go_display_settings TYPE REF TO cl_salv_display_settings.

*&---------------------------------------------------------------------*
* SELECTION-SCREEN
*&---------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1.
PARAMETERS: p_loop TYPE numc3.
PARAMETERS: p_dc01 TYPE i.
PARAMETERS: p_dc02 TYPE i.
SELECTION-SCREEN END OF BLOCK b1.

*&---------------------------------------------------------------------*
* START-OF-SELECTION
*&---------------------------------------------------------------------*
START-OF-SELECTION.

  PERFORM generate_test_data.
  PERFORM set_scenario.
  PERFORM process_scenario.
  PERFORM data_process_befor_output.
  PERFORM output.

*&---------------------------------------------------------------------*
*&      Form  GENERATE_TEST_DATA
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM generate_test_data .

  DATA: lo_random    TYPE REF TO cl_abap_random,
        lv_seed      TYPE i,
        lv_min_limit TYPE i,
        lv_max_limit TYPE i,
        lv_range_num TYPE i.

  DATA: ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  DATA: lv_i    TYPE i,
        lv_text TYPE text20.

  CONSTANTS c_char TYPE char50 VALUE '1234567890QWERTYUIOPASDFGHJKLZXCVBNM'.
  CONSTANTS c_text TYPE char80 VALUE '1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'.

  lv_seed = cl_abap_random=>seed( ).
  lo_random = cl_abap_random=>create( seed = lv_seed ).

  DO p_dc01 TIMES.
    CLEAR: ls_itab_01-key_char,ls_itab_01-key_num.
    ls_itab_01-int = sy-index.
    DO 2 TIMES.
      lv_min_limit = 0  .
      lv_max_limit = 35 .
      lv_range_num = lo_random->intinrange( low = lv_min_limit high = lv_max_limit ).
      ls_itab_01-key_char = ls_itab_01-key_char && c_char+lv_range_num(1).
    ENDDO.

    lv_min_limit = 0  .
    lv_max_limit = 999.
    lv_range_num = lo_random->intinrange( low = lv_min_limit high = lv_max_limit ).
    ls_itab_01-key_num = lv_range_num.

    lv_min_limit = 0  .
    lv_max_limit = 61 .
    lv_range_num = lo_random->intinrange( low = lv_min_limit high = lv_max_limit ).

    ls_itab_01-field_1 = lv_text && ls_itab_01-field_1.
    ls_itab_01-field_1+lv_i(1) = c_text+lv_range_num(1).
    APPEND ls_itab_01 TO gt_itab_01.

    lv_i    = lv_range_num MOD 30.
    lv_text = ls_itab_01-field_1+6(2).
  ENDDO.


  DO p_dc02 TIMES.
    CLEAR: ls_itab_02-key_char,ls_itab_02-key_num.
    ls_itab_02-int = sy-index.
    DO 2 TIMES.
      lv_min_limit = 0  .
      lv_max_limit = 35 .
      lv_range_num = lo_random->intinrange( low = lv_min_limit high = lv_max_limit ).
      ls_itab_02-key_char = ls_itab_02-key_char && c_char+lv_range_num(1).
    ENDDO.

    lv_min_limit = 0  .
    lv_max_limit = 999.
    lv_range_num = lo_random->intinrange( low = lv_min_limit high = lv_max_limit ).
    ls_itab_02-key_num = lv_range_num.

    lv_min_limit = 0  .
    lv_max_limit = 61 .
    lv_range_num = lo_random->intinrange( low = lv_min_limit high = lv_max_limit ).

    ls_itab_02-field_1 = lv_text && ls_itab_02-field_1.
    ls_itab_02-field_1+lv_i(1) = c_text+lv_range_num(1).
    APPEND ls_itab_02 TO gt_itab_02.

    lv_i    = lv_range_num MOD 30.
    lv_text = ls_itab_02-field_1+6(2).
  ENDDO.


ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  SET_SCENARIO
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM set_scenario .

  DATA ls_subroutines TYPE ty_subroutines.

  ls_subroutines-test_scenario  = '循环1表嵌套2表'.
  ls_subroutines-form           = 'LOOP2LOOP'.
  ls_subroutines-mode           = 1.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环2表嵌套1表'.
  ls_subroutines-form           = 'LOOP2LOOP'.
  ls_subroutines-mode           = 2.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环1表嵌套2排序表'.
  ls_subroutines-form           = 'LOOP2LOOP3'.
  ls_subroutines-mode           = 3.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环2表嵌套1排序表'.
  ls_subroutines-form           = 'LOOP2LOOP3'.
  ls_subroutines-mode           = 4.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环1表嵌套2表优化'.
  ls_subroutines-form           = 'LOOP2LOOP2'.
  ls_subroutines-mode           = 5.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环2表嵌套1表优化'.
  ls_subroutines-form           = 'LOOP2LOOP2'.
  ls_subroutines-mode           = 6.
  APPEND ls_subroutines TO gt_subroutines.


  ls_subroutines-test_scenario  = '循环1表读取2表'.
  ls_subroutines-form           = 'LOOP2READ'.
  ls_subroutines-mode           = 7.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环2表读取1表'.
  ls_subroutines-form           = 'LOOP2READ'.
  ls_subroutines-mode           = 8.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环1表二分读取2表'.
  ls_subroutines-form           = 'LOOP2READ2'.
  ls_subroutines-mode           = 9.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环2表二分读取1表'.
  ls_subroutines-form           = 'LOOP2READ2'.
  ls_subroutines-mode           = 10.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环1表key读取2表'.
  ls_subroutines-form           = 'LOOP2READ3'.
  ls_subroutines-mode           = 11.
  APPEND ls_subroutines TO gt_subroutines.

  ls_subroutines-test_scenario  = '循环2表key读取1表'.
  ls_subroutines-form           = 'LOOP2READ3'.
  ls_subroutines-mode           = 12.
  APPEND ls_subroutines TO gt_subroutines.


ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  PROCESS_SCENARIO
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM process_scenario .

  DATA: ls_subroutines TYPE ty_subroutines,
        ls_alv_out     TYPE ty_alv_out.

  DATA: lv_timestamp TYPE tzonref-tstampl.

  DO p_loop TIMES.
    LOOP AT gt_subroutines INTO ls_subroutines.
      ls_alv_out-loop_index    = sy-index.
      ls_alv_out-mode          = ls_subroutines-mode.
      ls_alv_out-test_scenario = ls_subroutines-test_scenario.
      GET TIME STAMP FIELD lv_timestamp.
      ls_alv_out-timestampb = lv_timestamp * 1000000.

      gv_mode = ls_subroutines-mode.
      PERFORM (ls_subroutines-form) IN PROGRAM.

      GET TIME STAMP FIELD lv_timestamp.
      ls_alv_out-timestampe = lv_timestamp * 1000000.
      ls_alv_out-spendtime  = ls_alv_out-timestampe - ls_alv_out-timestampb.
      APPEND ls_alv_out TO gt_alv_out.

    ENDLOOP.
  ENDDO.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  DATA_PROCESS_BEFOR_OUTPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM data_process_befor_output .

  DATA: lv_max_time TYPE i,
        lv_min_time TYPE i,
        lv_all_time TYPE p LENGTH 10,
        lt_alv_out  TYPE TABLE OF ty_alv_out,
        ls_alv_out  TYPE ty_alv_out.

  FIELD-SYMBOLS:  TYPE ty_alv_out.

  lt_alv_out = gt_alv_out.
  SORT lt_alv_out BY mode.
  ls_alv_out-loop_index = 0.
  MODIFY lt_alv_out FROM ls_alv_out TRANSPORTING loop_index WHERE loop_index <> 0.

  LOOP AT lt_alv_out ASSIGNING .

    IF lv_min_time IS INITIAL.
      lv_min_time = -spendtime.
    ENDIF.

    lv_max_time = nmax( val1 = lv_max_time val2 = -spendtime ).
    lv_min_time = nmin( val1 = lv_min_time val2 = -spendtime ).
    lv_all_time = lv_all_time + -spendtime.

    AT END OF test_scenario.
      CLEAR ls_alv_out.
      ls_alv_out-test_scenario = -test_scenario && ' Average'.
      IF p_loop <= 5 AND p_loop > 0.
        ls_alv_out-spendtime = lv_all_time / p_loop.
      ELSE.
        lv_all_time = lv_all_time - lv_max_time - lv_min_time.
        ls_alv_out-spendtime = lv_all_time / ( p_loop - 2 ).
      ENDIF.
      APPEND ls_alv_out TO gt_alv_out.
      CLEAR: lv_all_time,lv_max_time,lv_min_time.
    ENDAT.
  ENDLOOP.

ENDFORM.
*&---------------------------------------------------------------------*
*&      Form  OUT_PUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM output .

  DATA: lx_msg TYPE REF TO cx_salv_msg.

  TRY.
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table = go_salv
        CHANGING
          t_table      = gt_alv_out ).
    CATCH cx_salv_msg INTO lx_msg.
  ENDTRY.

*&------ SALV 展示格式设置
  go_display_settings = go_salv->get_display_settings( ).
  go_display_settings->set_fit_column_to_table_size( cl_salv_display_settings=>true ).

*------ 设置工具条
  go_salv->set_screen_status(
     pfstatus = 'STATUS'
     report = sy-repid
     set_functions = go_salv->c_functions_all ).

*&----- 输出数据
  go_salv->display( ).

ENDFORM.

FORM loop2loop.

  DATA: lt_itab_01 TYPE TABLE OF ty_test_itab,
        lt_itab_02 TYPE TABLE OF ty_test_itab,
        ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  IF gv_mode MOD 2 = 1.
    lt_itab_01 = gt_itab_01.
    lt_itab_02 = gt_itab_02.
  ELSE.
    lt_itab_01 = gt_itab_02.
    lt_itab_02 = gt_itab_01.
  ENDIF.

  LOOP AT lt_itab_01 INTO ls_itab_01.
    LOOP AT lt_itab_02 INTO ls_itab_02 WHERE key_char = ls_itab_01-key_char AND key_num = ls_itab_01-key_num.
      ls_itab_01-field_1 = ls_itab_02-field_1.
    ENDLOOP.
  ENDLOOP.

ENDFORM.
FORM loop2loop2.

  DATA: lt_itab_01 TYPE TABLE OF ty_test_itab,
        lt_itab_02 TYPE TABLE OF ty_test_itab,
        ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  DATA: lv_from TYPE sy-tabix.

  IF gv_mode MOD 2 = 1.
    lt_itab_01 = gt_itab_01.
    lt_itab_02 = gt_itab_02.
  ELSE.
    lt_itab_01 = gt_itab_02.
    lt_itab_02 = gt_itab_01.
  ENDIF.

  SORT lt_itab_02 BY key_char key_num.
  LOOP AT lt_itab_01 INTO ls_itab_01.
    READ TABLE lt_itab_02 TRANSPORTING NO FIELDS WITH KEY key_char = ls_itab_01-key_char key_num = ls_itab_01-key_num BINARY SEARCH.
    IF sy-subrc = 0.
      lv_from = sy-tabix.
      LOOP AT lt_itab_02 INTO ls_itab_02  FROM lv_from.
        IF ls_itab_02-key_char <> ls_itab_01-key_char OR ls_itab_02-key_num <> ls_itab_01-key_num.
          EXIT.
        ENDIF.
        ls_itab_01-field_1 = ls_itab_02-field_1.
      ENDLOOP.
    ENDIF.
  ENDLOOP.

ENDFORM.

FORM loop2loop3.

  DATA: lt_itab_01 TYPE TABLE OF ty_test_itab,
        lt_itab_02 TYPE TABLE OF ty_test_itab,
        lt_itab_03 TYPE SORTED TABLE OF ty_test_itab WITH NON-UNIQUE KEY primary_key COMPONENTS key_char key_num,
        ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  IF gv_mode MOD 2 = 1.
    lt_itab_01 = gt_itab_01.
    lt_itab_02 = gt_itab_02.
  ELSE.
    lt_itab_01 = gt_itab_02.
    lt_itab_02 = gt_itab_01.
  ENDIF.

*  SORT lt_itab_02 BY key_char key_num.
  lt_itab_03 = lt_itab_02.
  LOOP AT lt_itab_01 INTO ls_itab_01.
    LOOP AT lt_itab_03 INTO ls_itab_02 USING KEY primary_key WHERE key_char = ls_itab_01-key_char AND key_num = ls_itab_01-key_num.
      ls_itab_01-field_1 = ls_itab_02-field_1.
    ENDLOOP.
  ENDLOOP.

ENDFORM.

FORM loop2read.

  DATA: lt_itab_01 TYPE TABLE OF ty_test_itab,
        lt_itab_02 TYPE TABLE OF ty_test_itab,
        ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  IF gv_mode MOD 2 = 1.
    lt_itab_01 = gt_itab_01.
    lt_itab_02 = gt_itab_02.
  ELSE.
    lt_itab_01 = gt_itab_02.
    lt_itab_02 = gt_itab_01.
  ENDIF.

  LOOP AT lt_itab_01 INTO ls_itab_01.
    READ TABLE lt_itab_02 INTO ls_itab_02 WITH KEY key_char = ls_itab_01-key_char key_num = ls_itab_01-key_num.
    IF sy-subrc = 0.
      ls_itab_01-field_1 = ls_itab_02-field_1.
    ENDIF.
  ENDLOOP.

ENDFORM.
FORM loop2read2.

  DATA: lt_itab_01 TYPE TABLE OF ty_test_itab,
        lt_itab_02 TYPE TABLE OF ty_test_itab,
        lt_itab_03 TYPE TABLE OF ty_test_itab,
        ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  IF gv_mode MOD 2 = 1.
    lt_itab_01 = gt_itab_01.
    lt_itab_02 = gt_itab_02.
  ELSE.
    lt_itab_01 = gt_itab_02.
    lt_itab_02 = gt_itab_01.
  ENDIF.

  SORT lt_itab_02 BY key_char key_num.
  lt_itab_03 = lt_itab_02.
  LOOP AT lt_itab_01 INTO ls_itab_01.
    READ TABLE lt_itab_02 INTO ls_itab_02 WITH KEY key_char = ls_itab_01-key_char key_num = ls_itab_01-key_num BINARY SEARCH.
    IF sy-subrc = 0.
      ls_itab_01-field_1 = ls_itab_02-field_1.
    ENDIF.
  ENDLOOP.

ENDFORM.
FORM loop2read3.

  DATA: lt_itab_01 TYPE TABLE OF ty_test_itab,
        lt_itab_02 TYPE TABLE OF ty_test_itab,
        lt_itab_03 TYPE SORTED TABLE OF ty_test_itab WITH NON-UNIQUE KEY primary_key COMPONENTS key_char key_num,
        ls_itab_01 TYPE ty_test_itab,
        ls_itab_02 TYPE ty_test_itab.

  IF gv_mode MOD 2 = 1.
    lt_itab_01 = gt_itab_01.
    lt_itab_02 = gt_itab_02.
  ELSE.
    lt_itab_01 = gt_itab_02.
    lt_itab_02 = gt_itab_01.
  ENDIF.

  SORT lt_itab_02 BY key_char key_num.
  lt_itab_03 = lt_itab_02.
  LOOP AT lt_itab_01 INTO ls_itab_01.
    READ TABLE lt_itab_03 INTO ls_itab_02 WITH KEY key_char = ls_itab_01-key_char key_num = ls_itab_01-key_num.
    IF sy-subrc = 0.
      ls_itab_01-field_1 = ls_itab_02-field_1.
    ENDIF.
  ENDLOOP.

ENDFORM.

 ABAP效率优化 LOOP循环嵌套效率分析_第5张图片

你可能感兴趣的:(ABAP,abap)