Speedup long running loop in ABAP

2.5k views Asked by At

When analysing a performance issue in an SAP export script (SAP R/3, 4.06b) I found the following code which runs about 10 minutes in the test system. Might be a bit faster in production but I can not test it there.

LOOP AT ZMARD.
  LOOP AT ZCOMB.
       IF ZCOMB-MATNR = ZMARD-MATNR.
          IF ZCOMB-LGORT = ZMARD-LGORT.
           IF ZCOMB-IND = ' '.
             IF ZCOMB-PLUMI = '+'.
                ZMARD-LABST = ZMARD-LABST + ZCOMB-MNG01.
             ELSEIF ZCOMB-PLUMI = '-'.
                ZMARD-LABST = ZMARD-LABST - ZCOMB-MNG01.
             ENDIF.                          "PLUMI
          ENDIF.                             "IND
        ENDIF.                               "LGORT
      ENDIF.                                 "MATNR
  ENDLOOP.
  IF ZMARD-LABST < 0.
     ZMARD-LABST = 0.
  ENDIF.
  WRITE ZMARD-LABST DECIMALS 0 TO ZMARD-ZLABST.
  MODIFY ZMARD.
ENDLOOP.

Do you have any advice on how this loops can be optimized / put together into one single loop?

5

There are 5 answers

4
icbytes On BEST ANSWER

Hello sure there are some possibilities.

First: You can conditionize the inner loop with "where outer looped struct-field" = "inner loop table field".

Second: You can use hashed tables. But those are more important for read table.

1
Nelson Miranda On

I think you should filter the inner loop. Additionally, I would define the tables as SORTED tables (WITH NON-SORTED KEY), at least 'ZCOMB' with keys MATNR, LGORT and perhaps IND.

I would also use a sy-tabix variable to keep the position of the row and use it then like this:

MODIFY ZMARD INDEX lv_tabix.

Or I could use a field symbol. It's your decision.

The code would look like this:

DATA lv_index type sy-tabix.

LOOP AT ZMARD.
  lv_index = sy-tabix.
  LOOP AT ZCOMB WHERE MATNR = ZMARD-MATNR AND LGORT = ZMARD-LGORT AND IND = ' '.
             IF ZCOMB-PLUMI = '+'.
                ZMARD-LABST = ZMARD-LABST + ZCOMB-MNG01.
             ELSEIF ZCOMB-PLUMI = '-'.
                ZMARD-LABST = ZMARD-LABST - ZCOMB-MNG01.
             ENDIF.                          "PLUMI
  ENDLOOP.
  IF ZMARD-LABST < 0.
     ZMARD-LABST = 0.
  ENDIF.
  WRITE ZMARD-LABST DECIMALS 0 TO ZMARD-ZLABST.
  MODIFY ZMARD INDEX lv_index.
ENDLOOP.

Finally, another approach, in my opinion, would be to loop over 'ZCOMB' and use COLLECT to sum the data and with those sums modify 'ZMARD' reading the table with BINARY SEARCH.

That, in my opinion, could improve the performance because you would loop only over one table.

Hope it helps.

1
Gert Beukema On

This is a classical case where two tables need to be intersected. The code in your example is one of the worst ways to do it and can be dramatically improved. Here is an interesting article about that.

And here is that implemented for your example.

Assumptions: 1. ZMARD has no duplicate entries for a MATNR LGORT combination; 2. The order of ZMARD is not important for the report;

DATA: IX_MARD TYPE I,
      IX_COMB TYPE I.

DEFINE READ_NEXT.
  ADD 1 TO IX_&1.
  READ TABLE Z&1 INDEX IX_&1.
  IF SY-SUBRC NE 0.
    IX_&1 = -1.
  ENDIF.
END-OF-DEFINITION.

" It would be better if these sorts were done
" in the SELECT of the data
SORT ZMARD BY MATNR LGORT.
SORT ZCOMB BY MATNR LGORT.

IX_MARD = IX_COMB = 0.
READ_NEXT: MARD, COMB.

WHILE IX_MARD GT 0 AND IX_COMB GT 0.

  IF ZMARD-MATNR EQ ZCOMB-MATNR AND
     ZMARD-LGORT EQ ZCOMB-LGORT.
    " Match between MARD and COMB
    IF ZCOMB-IND = ' '.
      IF ZCOMB-PLUMI = '+'.
         ZMARD-LABST = ZMARD-LABST + ZCOMB-MNG01.
      ELSEIF ZCOMB-PLUMI = '-'.
         ZMARD-LABST = ZMARD-LABST - ZCOMB-MNG01.
      ENDIF.                          "PLUMI
    ENDIF.                             "IND

    READ_NEXT: COMB.

  ELSEIF ZMARD-MATNR LT ZCOMB-MATNR OR
         ( ZMARD-MATNR EQ ZCOMB-MATNR AND
           ZMARD-LGORT LT ZCOMB-LGORT
         ).
    " MARD behind COMB
    IF ZMARD-LABST < 0.
       ZMARD-LABST = 0.
    ENDIF.
    WRITE ZMARD-LABST DECIMALS 0 TO ZMARD-ZLABST.
    MODIFY ZMARD INDEX IX_MARD.

    READ_NEXT MARD.

  ELSE.
    " MARD ahead of COMB
    READ_NEXT COMB.

  ENDIF. " Match on material and storage location

ENDWHILE.

WHILE IX_MARD GT 0.
  IF ZMARD-LABST < 0.
     ZMARD-LABST = 0.
  ENDIF.
  WRITE ZMARD-LABST DECIMALS 0 TO ZMARD-ZLABST.
  MODIFY ZMARD INDEX IX_MARD.
  READ_NEXT MARD.
ENDWHILE.

Please note that I just edited this code here so I expect it will have some syntax errors and bugs.

2
tomdemuyt On

It seems this code wants to update ZMARD-LABST for all entries in ZMARD. Looping over table within a table loop seems really slow to do that. If you sorted ZMARD on MATNR, then you could do this in 2 distinct loops. Something like

DATA lv_tabix type sy-tabix.
FIELD-SYMBOLS <MARD> like ZMARD. "I dont know the type of ZMARD

SORT ZCOMB BY MATNR.    
LOOP AT ZCOMB.
  IF ZCOMB-IND NE ' '.
    CONTINUE.
  ENDIF.
  IF <MARD>-MATNR NE ZCOMB-MATNR OR <MARD>-LGORT NE ZCOMB-LGORT. 
    "This could be a binary read, because of sorting we only do a read per material
    READ TABLE MARD ASSIGNING <MARD> WITH KEY MATNR = ZCOMB-MATNR LGORT = ZCOMB-LGORT.
    IF SY-SUBRC NE 0.
      CONTINUE.
    ENDIF.
  ENDIF.
  IF ZCOMB-PLUMI = '+'.
    ADD ZCOMB-MNG01 TO <MARD>-LABST.
  ELSEIF ZCOMB-PLUMI = '-'.
    SUBTRACT ZCOMB-MNG01 FROM <MARD>-LABST.
  ENDIF.
ENDLOOP.


LOOP AT ZMARD.
  lv_index = sy-tabix.
  IF ZMARD-LABST < 0.
     ZMARD-LABST = 0.
  ENDIF.
  WRITE ZMARD-LABST DECIMALS 0 TO ZMARD-ZLABST.
  MODIFY ZMARD INDEX lv_tabix.
ENDLOOP.

Note that MARD has 3 keys: MATNR, BUKRS, and LGORT. I hope that either your LGORT values do not cross WERKS or that ZCOMB has a WERKS field that you can use.

0
user5056301 On

If you don't care about execution order you can do this (untested):

SORT ZMARD BY MATNR LGORT.
SORT ZCOMB BY MATNR LGORT.

LOOP AT ZMARD ASSIGNING <ZMARD>.

READ TABLE ZCOMB WITH KEY
                MATNR = <ZMARD>-MATNR
                LGORT = <ZMARD>-LGORT
                TRANSPORTING NO FIELDS.
IF sy-subrc = 0.                            
    lv_index = sy-tabix.
ELSE.
    CONTINUE.
ENDIF.

LOOP AT ZCOMB ASSIGNING <ZCOMB> FROM lv_index.

    IF <ZCOMB>-MATNR <> <ZMARD>-MATNR OR <ZCOMB>-LGORT <> <ZMARD>-LGORT.
        EXIT.
    ENDIF.

    IF <ZCOMB>-IND = ' '.
        IF <ZCOMB>-PLUMI = '+'.
            <ZMARD>-LABST = <ZMARD>-LABST + <ZCOMB>-MNG01.
        ELSEIF ZCOMB-PLUMI = '-'.
            <ZMARD>-LABST = <ZMARD>-LABST - <ZCOMB>-MNG01.
        ENDIF.
    ENDIF.

ENDLOOP. " ZCOMB loop

ENDLOOP. " ZMARD loop