LOOP ... INTO is faster than LOOP ... ASSIGNING!
LOOP ... INTO is faster than LOOP ... ASSIGNING!

LOOP ... INTO is faster than LOOP ... ASSIGNING!

Published at May 30, 2023by Jörg Brandeis

The following texts and content on this page were partially or completely generated with the help of generative AI models.

A question as old as ABAP programming itself1: Which is better? LOOP .. INTO, LOOP .. ASSIGNING, or LOOP ... REFERENCE INTO2? Runtime often plays a big role in this discussion. But people frequently forget that the loop also has a loop body, which is executed on every single iteration. And that what matters is the total runtime. That's why I made a few measurements for this blog post. And in doing so I arrived at a surprising result. Because LOOP ... INTO is often the fastest variant!

The focus of this article is on read access. If the data in the internal table is being changed, then ASSIGNING is undisputedly the better choice. Other aspects, such as readability or Clean Code, I have left out of this article. I'll deal with those in my next article.

The question of LOOP or ASSIGNING comes up in almost every one of my trainings on modern ABAP or Clean ABAP, but sometimes in projects too. Some of the arguments used are not entirely correct, and the discussion is often conducted very dogmatically. That's why I wanted to make the discussion a bit more objective and to back up my most important argument: the loop body is dominant, so the variant plays a subordinate role. That I would even find INTO to often be better, though, is something I hadn't counted on.

Overview of the comparison

An empty loop is pointless. Nevertheless, it forms the basis of many a comment or article.3 To avoid that happening to us, we need something that happens in the loop body. I have outsourced that into the method DO_SOMETHING. In the section Complexity I have described the scenarios that are controlled by the parameter Compexity.

DO 3 TIMES.

  DATA(lv_complexity) = sy-index.

  LOOP AT mt_data INTO DATA(ls_data).
    CHECK lv_complexity > 1.
    do_something( Complexity = lv_Complexity
                  is_data    = ls_data ).
  ENDLOOP.

ENDDO.

And because during the tests I noticed that the method call also has a significant influence on the runtime, I additionally tested a 4th scenario with the same complexity but without a method call.

The dimensions

  • Width of the table/structure
  • Complexity in the loop body
  • Number of records
  • LOOP variants

These are discussed in detail below.

The width of the structure

  • Narrow (6 fields, 54 bytes)
  • Many bytes (22 fields, 2518 bytes)
  • Many fields (50 fields, 996 bytes)

Complexity

In these test series we consistently have low complexity. The tasks performed in these examples are considerably smaller than the loop bodies of the vast majority of LOOP loops in the wild. In practice, I have seen loops that contain well over 2000 lines of code. Here are our four scenarios:

  1. Nothing - Directly in the LOOP a CHECK that aborts the iteration
  2. Call of an empty method
  3. Some pointless logic, distributed across 4 methods: assigning two components of the structure, a calculation with DATS values including a function module call, two comparisons in an IF branch. See the code on the right...
  4. The same pointless logic directly, without methods around it. This test case was added afterwards and cannot be seen in the code on the right. The reason for this was that we observed that method calls are unfavorable. To quantify that, we also built a test case with complexity but entirely without a method call.

Number of records

The number of records was varied on a trial basis. As expected, the runtimes developed linearly with the number of records for all scenarios. We therefore fixed the number of records at 50000. That corresponds to the typical package size of a DTP in SAP BW during ABAP execution.

  METHOD do_something.

    CHECK Complexity > 1.
    do_nothing( ).

    CHECK Complexity > 2.
    do_assign_components( is_data ).
    do_date_things( is_data ).
    do_comparisons( is_data ).

  ENDMETHOD.

   METHOD do_assign_components.

    ASSIGN COMPONENT 'BUDAT'
      OF STRUCTURE is_data
      TO FIELD-SYMBOL(<date>).

    ASSIGN COMPONENT 'SUMMARY'
      OF STRUCTURE is_data
      TO FIELD-SYMBOL(<field>).

  ENDMETHOD.

  METHOD do_date_things.
    DATA day TYPE cind.
    DATA(Tomorrow) = is_data-created_on .

    Tomorrow = Tomorrow + 1.

    CALL FUNCTION 'DATE_COMPUTE_DAY'
      EXPORTING date = Tomorrow
      IMPORTING day  = Day.

    DATA(DaysUntilTomorrow) = Tomorrow - sy-datum.

  ENDMETHOD.

  METHOD do_comparisons.
    IF is_data-title = 'Morbi vel '.

    ENDIF.

    IF is_data-title CA 'ABC'.

    ENDIF.
  ENDMETHOD.

LOOP variants

The LOOP variant is the actual subject of research. I'll describe them here again briefly, even though most readers already know this:

  1. LOOP ... INTO wa - Corresponds to a loop in which the current row is copied into the work area wa on each iteration.
  2. LOOP ... ASSIGNING <fs> - Creates no copy of the data. Instead, only the field symbol <fs> will point to it. This variant is particularly advantageous when the data of the table is to be changed, because the field symbol points to the data of the table.
  3. LOOP ... REFERENCE INTO ref - Also creates no copy of the data. Instead, a reference to the row in the table is written into the field ref.

LOOP ... INTO

LOOP AT mt_data INTO ls_data.


  CHECK lv_complexity > 1.
  do_something( 
    Complexity = lv_Complexity
    is_data    = ls_data ).
ENDLOOP.

LOOP ... ASSIGNING

LOOP AT mt_data ASSIGNING <ls_data>.
  

  CHECK lv_complexity > 1.
  do_something( 
    Complexity = lv_Complexity
    is_data    = <ls_data> ).
ENDLOOP.

LOOP ... REFERENCE INTO

LOOP AT mt_data REFERENCE 
                INTO lr_data.

  CHECK lv_complexity > 1.
  do_something( 
    Complexity = lv_Complexity
    is_data    = lr_data->* ).
ENDLOOP.

Measurements

Narrow structure

6 fields, 54 bytes

Approach\ComplexityEmpty, just CheckEmpty Method CallSome Logic in MethodsSome Logic
INTO3 ms15 ms109 ms74 ms
ASSIGNING2 ms20 ms147 ms87 ms
REFERENCE2 ms22 ms151 ms93 ms

Wide structure - many bytes:

22 fields, 2518 bytes

Approach\ComplexityEmpty, just CheckEmpty Method CallSome Logic in MethodsSome Logic
INTO21 ms32 ms128 ms91 ms
ASSIGNING2 ms21 ms153 ms91 ms
REFERENCE2 ms22 ms155 ms97 ms

Wide structure - many fields:

50 fields, 996 bytes

Approach\ComplexityEmpty, just CheckEmpty Method CallSome Logic in MethodsSome Logic
INTO7 ms18 ms110 ms76 ms
ASSIGNING2 ms21 ms152 ms91 ms
REFERENCE2 ms22 ms156 ms96 ms

Observations

Stable execution times

Across all executions the runtimes were relatively constant and all results reproducible.

Constant runtimes with INTO REFERENCE and ASSIGNING

With both approaches there is no dependency on the width of the structure. That is to be expected, since the data is not copied.

Runtimes of LOOP ... INTO depend on the width of the structure

More precisely, they appear to be linear to the width in bytes. The number of fields, on the other hand, apparently has no influence. In our examples this goes up to 2.5 kb. But an EKPO tips the scales at almost 6 kb of width. It's already questionable whether you need all that glory or whether a subset wouldn't have sufficed. We wanted to avoid SELECT *, didn't we? At least if we're talking seriously about performance.

The variant LOOP ... ASSIGNING is always faster than LOOP ... REFERENCE INTO

This statement has been discussed and proven many times already. But it can also be nicely observed in our tests. The difference, however, is marginal.

Ratio of loop to loop body.

Even if the loop with an empty loop body in the most unfavorable case with a very wide structure takes a maximum of 21 ms with LOOP ... INTO, the loop body is dominant even for the smallest tasks. Even the call of an empty method takes about 10 ms. With small calculations or comparisons we quickly reach 100 ms.

==> It's not the loop that's slow, but what happens inside the loop. Because it is executed X times.

Access to field symbols and references in LOOP loops is slower

In all measurements I observed that using field symbols or references to a current row of the internal table is slower than a structure. This applies in particular to method calls. In variant 4 this is even 40 ms. Even with a very wide structure INTO is faster.

I can't explain it. I would be happy to receive attempts at explanation or counter-evidence.

A cross-check with a DO loop showed that this phenomenon only occurs in the LOOP. Normally, structures, field symbols, and references are almost identical in access speed. For the same logic with complexity 4 I measured the following values:

Data typeAccess time
Structure106 ms
Field symbol106 ms
Reference108 ms

With non-empty loop bodies LOOP ... INTO is the fastest

This observation is remarkable. Because it contradicts the widespread belief that LOOP ... ASSIGNING or LOOP ... INTO REFERENCE is faster. ASSIGNING only comes out ahead when the following two conditions come together:

  • The structure is very wide and
  • Almost no logic takes place in the LOOP

Conclusion and assessment

The sweeping statement "LOOP ... ASSIGNING is the fastest" is wrong. As soon as you're dealing with real requirements, LOOP ... INTO is in fact faster. At least as long as the width of the table is within a reasonable measure. But very wide internal tables should practically no longer be used4. And loops with an empty loop body are pointless.

More important, though, is the insight that the LOOP variant actually has hardly any relevant influence on the runtime. Because the loop body is almost always dominant. In practice, performance problems don't come from someone choosing the wrong LOOP variant. Because the time complexity here is always O(n). Most of the performance problems I have analyzed in my career, however, had a time complexity of O(n²). Typical candidates are, for example:

  • READ inside a LOOP with an unsuitable table type or key
  • SELECT inside a LOOP
  • An unfavorable program structure

Precisely because the result with the faster LOOP ... INTO is unexpected, I would be happy to receive feedback. Maybe someone has a passable explanation for it? Something must still be happening in the background that isn't obvious. Because the pure LOOP without data processing is faster with ASSIGNING. And why exactly are method calls unfavorable? Questions upon questions...

Footnotes

  1. The claim "a question as old as ABAP programming itself" is probably wrong, since I can't assume that field symbols were included in the first ABAP version. But the oldest release notes about ABAP versions that I could find refer to version 3.0, and there the field symbols are already present. See, for example, this note from the ABAP documentation 7.51. Still a nice introduction.

  2. In this article I write, for simplicity, LOOP ... ASSIGNING, which always includes LOOP ... REFERENCE INTO, as far as I haven't differentiated further. Because both variants have very similar behavior.

  3. An example of empty loops with a clear result can be found in this discussion: https://answers.sap.com/questions/7873994/performance-with-field-symbols.html

  4. Since you should no longer read the entire width of a DB table with SELECT *, corresponding internal tables with the maximum width are also unnecessary. In the interest of Clean Code, you should always have only the fields in a structure or internal table that are actually relevant for the current task. Referring to existing, extremely wide DDIC objects (DB tables, structures, or views) is convenient, but it makes readability more difficult. The same applies when you refer to the modern CDS views of the VDM.


Useful links

More articles

New!
New!