Technology Blog Posts by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
Michał_Biegun
Explorer
2,015

Recently, I needed to move lines from one table to another. I decided to check which method is the fastest (most performant). Below is my analysis — but first, a few comments on the methodology.

 

Methodology

  1. When comparing how AS ABAP processes a set of data (in particular, a table), I realized that the method executed first is always slower. Therefore, I use run_dummy() to eliminate this issue.

  2. I use DO n TIMES for the same reason. If I called each method only once, the first one would take twice as much time as the second (proof at the end of the blog).

  3. I ran each test multiple times and present here the results that consistently occurred.

  4. First, I tested with a sorted table using a unique key, and then with a standard table without a key. Without a key, I couldn’t use FILTER.

  5. The first set of tests was performed on ECC 7.5. The second was repeated on BTP Steampunk, although with a different table type (VBRP and SFLIGHT), so direct comparison of results from ECC and BTP is impossible.

 

ECC tests

Just paste this into any report and it should run. In the second case, the key definition and the run_filter() method were removed.

CLASS main DEFINITION CREATE PUBLIC.

  PUBLIC SECTION.
    constants runs TYPE i VALUE 1000.

    METHODS: constructor,
      run_dummy,
      run_loop,
      run_for,
      run_filter.

  PRIVATE SECTION.
    DATA all_items TYPE SORTED TABLE OF vbrp WITH UNIQUE KEY vbeln posnr.

ENDCLASS.

CLASS main IMPLEMENTATION.
  METHOD constructor.
    SELECT * FROM vbrp UP TO 20000 ROWS
      INTO TABLE _items
     ORDER BY vbeln DESCENDING.
  ENDMETHOD.

  METHOD run_dummy.
    LOOP AT all_items ASSIGNING FIELD-SYMBOL(<item>).
    ENDLOOP.
  ENDMETHOD.

  METHOD run_for.
    DATA: selected_items TYPE TABLE OF vbrp.
    selected_items = VALUE #( FOR item IN all_items
                                WHERE ( vbeln = '0080011871' )
                                ( item )
                            ).
  ENDMETHOD.

  METHOD run_loop.
    DATA: selected_items TYPE TABLE OF vbrp.
    LOOP AT all_items ASSIGNING FIELD-SYMBOL(<item>) WHERE vbeln = '0080011871'.
      APPEND <item> TO selected_items.
    ENDLOOP.
  ENDMETHOD.

  METHOD run_filter.
    DATA: selected_items TYPE TABLE OF vbrp.
    selected_items = FILTER #( all_items USING KEY primary_key WHERE vbeln = '0080011871' ).
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
DATA(main) = new main( ).

do main->runs TIMES.
  main->run_dummy( ).
ENDDO.

do main->runs TIMES.
  main->run_loop( ).
ENDDO.

do main->runs TIMES.
  main->run_filter( ).
ENDDO.

do main->runs TIMES.
  main->run_for( ).
ENDDO.
ENDCLASS.

Results: sorted table, 1000 executions

Micha_Biegun_0-1744417035935.png

I've made about a dozen runs. This result is representative of all of them (they didn't differ). LOOP is always the fastest, but FILTER is always right behind (difference is insignificant). VALUE FOR however is 50% slower.

Results: standard table, 1000 executions

Micha_Biegun_1-1744417133596.png

Micha_Biegun_2-1744417144037.png

I've made probably about 30 test runs. Results varied slightly but all of them can be summed up to two trends presented above.

Initially, LOOP was faster. A few minutes later, FOR overtook it, only to lose its lead again later. I have no idea what caused this fluctuation. In any case, the difference between both methods is minimal.

 

BTP Steampunk Test

This test used a different table: the all-time favorite /DMO/FLIGHT. Unfortunately, my BTP version doesn’t offer performance profiling (trace), so I had to use timestamps, which might introduce small measurement errors.

CLASS ycl_test_loops DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

    CONSTANTS runs TYPE i VALUE 1000.

  PRIVATE SECTION.
    DATA: all_items TYPE STANDARD TABLE OF /dmo/flight WITH NON-UNIQUE SORTED KEY ki COMPONENTS connection_id,
          connid TYPE /dmo/connection_id   VALUE '0407'.
    METHODS: prepare_data,
      run_dummy,
      run_loop,
      run_for,
      run_filter.

ENDCLASS.


CLASS ycl_test_loops IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.
    DATA: start TYPE timestampl,
          end   TYPE timestampl.

    me->prepare_data( ).

    GET TIME STAMP FIELD start.
    DO runs TIMES.
      me->run_dummy( ).
    ENDDO.
    GET TIME STAMP FIELD end.
    out->write( |dummy: { end - start }| ).


    GET TIME STAMP FIELD start.
    DO runs TIMES.
      me->run_loop( ).
    ENDDO.
    GET TIME STAMP FIELD end.
    out->write( |loop: { end - start }| ).


    GET TIME STAMP FIELD start.
    DO runs TIMES.
      me->run_for( ).
    ENDDO.
    GET TIME STAMP FIELD end.
    out->write( |for: { end - start }| ).


    GET TIME STAMP FIELD start.
    DO runs TIMES.
      me->run_filter( ).
    ENDDO.
    GET TIME STAMP FIELD end.
    out->write( |filter: { end - start }| ).

  ENDMETHOD.


  METHOD prepare_data.
    DO 500 TIMES. "I have only 40 entries in DB, so 40 * 500 gives me 20k entries
      SELECT * FROM /dmo/flight
      APPENDING TABLE _items.
    ENDDO.
  ENDMETHOD.


  METHOD run_dummy.
    LOOP AT all_items ASSIGNING FIELD-SYMBOL(<item>).
    ENDLOOP.
  ENDMETHOD.


  METHOD run_filter.
    DATA: selected_items TYPE TABLE OF /dmo/flight.
    selected_items = FILTER #( all_items USING KEY ki WHERE connection_id = connid ).
  ENDMETHOD.


  METHOD run_for.
    DATA: selected_items TYPE TABLE OF /dmo/flight.
    selected_items = VALUE #( FOR item IN all_items USING KEY ki
                              WHERE ( connection_id = connid )
                              ( item )
                            ).
  ENDMETHOD.


  METHOD run_loop.
    DATA: selected_items TYPE TABLE OF /dmo/flight.
    LOOP AT all_items ASSIGNING FIELD-SYMBOL(<item>) USING KEY ki
         WHERE connection_id = connid.
      APPEND <item> TO selected_items.
    ENDLOOP.
  ENDMETHOD.
ENDCLASS.

Results: Standard Table, 1000 Executions

The results are in seconds.

dummy: 3.5287930
loop:  1.3220800
for:   1.3824890

dummy: 3.5545590
loop:  1.3494420
for:   1.3473680

 As in ECC, there’s no noticeable difference between LOOP and VALUE FOR.

Results: Sorted key, 1000 Executions

The results are in seconds.

dummy:  3.5353230
loop:   0.4449050
for:    0.5068880
filter: 0.0389550

dummy:  3.5583420
loop:   0.4789230
for:    0.4469660
filter: 0.0380170

dummy:  3.4402150
loop:   0.4193350
for:    0.4578140
filter: 0.0394450

Here things get interesting. Again, FOR and LOOP are very close — but what shocked me is that FILTER is 10x faster! If anyone could double-check my results, please let me know in the comments if I made a mistake.

 

Conclusions

On ECC with sorted keys:

  • LOOP was consistently the fastest,
  • FILTER was nearly as fast — arguably equivalent,
  • VALUE FOR was 50% slower (still acceptable, but noticeably slower)

On BTP with sorted keys:

  • LOOP and FOR perform almost identically.

  • FILTER is in a league of its own, being over 10 times faster.

Others:

When working with standard tables, there’s virtually no difference between LOOP and VALUE FOR. On BTP, LOOP is slightly better.

Using a sorted key reduced search time by ~20–30x on ECC, and ~3x on BTP. On BTP, combining a sorted key with FILTER reduced execution time by a factor of 45.

Interestingly, the SELECT operation is not affected by the key definition — so initializing the sorted key costs nothing. Naturally, modifying the table means the key must be rebuilt, which adds overhead. But if all_items is read-only, a sorted key makes a massive difference.

It’s clear that performance optimizations differ between systems. You can’t write code that’s optimal for both ECC and BTP. And it’s not just about DB storage types — even pure ABAP operations behave differently. On BTP (and likely in all ABAP Cloud environments), FILTER is a game changer. On ECC, it offers little to no advantage—aside from its more concise syntax.

 

Discussion

I see no value in the VALUE FOR statement (pun intended). Its syntax is unintuitive and has too many brackets. Also, it’s not debuggable, which makes troubleshooting more complex scenarios a pain in the...

VALUE requires a static type (# is statically derived from context), whereas LOOP supports dynamic types determined at runtime (using field symbols and ASSIGN COMPONENT). In general, we should prefer static typing, but cases where it has to be used, LOOP remains the only way.

FILTER has a clear and concise syntax and is shorter than LOOP. Most importantly, it's significantly faster in ABAP Cloud. It forces us to use sorted keys, while LOOP allows us to be lazy.

On ECC, I used a sorted table. On Steampunk, I used a standard table with a sorted key — so some differences might stem from this.

Proof for Point 2 in Methodology

For standard tables, running a tested method only once causes results to heavily depend on the order of method calls — even if run_dummy() is used:

Micha_Biegun_0-1744568216811.png

Micha_Biegun_1-1744568227052.png

Someone once said: "The last shall be first, and the first last" ;).

Anyways, when testing performance, be careful not to be fooled by call order.

9 Comments
Sandra_Rossi
Active Contributor

You are not comparing the same thing between "VALUE #( FOR item" and "LOOP AT ASSIGNING <item>". You should compare with field symbols in both (i.e. "VALUE #( FOR <item>").

Jelena_Perfiljeva
Active Contributor

Michal, thanks for spending time on this investigation and analysis! I always appreciate seeing the actual data instead of unsubstantiated claims that something is "better".

It would be great if you could add general ABAP Development tag to this post. My feed reader didn't pick up this post and I've just stumbled upon it by accident. I think it's worth sharing more widely.

Thanks again!

Michał_Biegun
Explorer
0 Kudos

@Sandra_Rossigood point. I've quickly extended my code to run both LOOP and VALUE on variable and on fs. I'm a bit lazy, so I checked it only on sorted tables (results are at the end of the comment).

Truth be told, it doesn't change much. Field symbol improves the performance:

  • on Cloud, by 2-10% which is almost a measuring error tolerance (and that's a surprise for me)
  • on ECC, by solid 15-20%.

FOR remains slower than LOOP by 25-30% on ECC.

Thanks to your remark, my "research" became more thorough, so thank you for that. However, my conclusions stay the same.

Results for sorted on Cloud:

dummy: 3.4174460
loop : 0.4627110
loop fs: 0.4552510
for: 0.4856020
for fs: 0.4326170
filter: 0.0383610


dummy: 3.4830190
loop : 0.4651930
loop fs: 0.4248510
for: 0.4427570
for fs: 0.4170480
filter: 0.0384160


dummy: 3.4850880
loop : 0.4527180
loop fs: 0.4741240
for: 0.4927440
for fs: 0.4652430
filter: 0.0528900

Results for sorted on ECC:

Micha_Biegun_0-1745008670231.png

 

Michał_Biegun
Explorer
0 Kudos

@Jelena_Perfiljevatag added, but I wonder if I even write in the correct group (technology blogs by members). I think I should put it in "Application Development and Automation".

Sandra_Rossi
Active Contributor

Quite impressive. I prefer to do my own test below, with data independent from database to be reproducible by anybody, but get the same result:

Sandra_Rossi_0-1745051905072.png

FILTER, I understand that it's faster probably due to an implementation in the kernel (but still impressed by the big difference with others).

I don't understand why FOR is so much slower than LOOP AT.

Test done with ABAP 7.58, kernel 793 SP 51 (on premises).

Code (run the test as ABAP Unit):

CLASS main DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.

  PRIVATE SECTION.
    CONSTANTS runs TYPE i VALUE 500000.

    CLASS-METHODS class_setup.

    METHODS run_for         FOR TESTING RAISING cx_static_check.
    METHODS run_loop_append FOR TESTING RAISING cx_static_check.
    METHODS run_loop_insert FOR TESTING RAISING cx_static_check.
    METHODS run_filter      FOR TESTING RAISING cx_static_check.

    TYPES:
      BEGIN OF ts_perf_test,
        vbeln TYPE vbrp-vbeln,
        posnr TYPE vbrp-posnr,
      END OF ts_perf_test.
    TYPES tt_perf_test TYPE SORTED TABLE OF ts_perf_test WITH UNIQUE KEY vbeln posnr.

    CLASS-DATA table_perf_test TYPE tt_perf_test.
    CLASS-DATA vbeln_anywhere  TYPE vbrp-vbeln.

ENDCLASS.


CLASS main IMPLEMENTATION.
  METHOD class_setup.
    DATA(table_perf_test) = VALUE tt_perf_test( FOR vbeln = 1 WHILE vbeln <= 5000
                                                FOR posnr = 1 WHILE posnr <= 4
                                                ( vbeln = vbeln
                                                  posnr = posnr ) ) ##NEEDED.
    vbeln_anywhere = 333.
  ENDMETHOD.

  METHOD run_for.
    DO runs TIMES.
      DATA(selected_items) = VALUE tt_perf_test( FOR <item> IN table_perf_test
                                                 WHERE ( vbeln = vbeln_anywhere )
                                                 ( <item> ) ) ##NEEDED.
    ENDDO.
  ENDMETHOD.

  METHOD run_loop_append.
    DO runs TIMES.
      DATA(selected_items) = VALUE tt_perf_test( ) ##NEEDED.
      LOOP AT table_perf_test ASSIGNING FIELD-SYMBOL(<item>) WHERE vbeln = vbeln_anywhere.
        APPEND <item> TO selected_items.
      ENDLOOP.
    ENDDO.
  ENDMETHOD.

  METHOD run_loop_insert.
    DO runs TIMES.
      DATA(selected_items) = VALUE tt_perf_test( ) ##NEEDED.
      LOOP AT table_perf_test ASSIGNING FIELD-SYMBOL(<item>) WHERE vbeln = vbeln_anywhere.
        INSERT <item> INTO TABLE selected_items.
      ENDLOOP.
    ENDDO.
  ENDMETHOD.

  METHOD run_filter.
    DO runs TIMES.
      DATA(selected_items) = FILTER tt_perf_test( table_perf_test WHERE vbeln = vbeln_anywhere ) ##NEEDED.
    ENDDO.
  ENDMETHOD.
ENDCLASS.

 

Sandra_Rossi
Active Contributor

I looked at the byte codes generated for each method:

Comparison between "VALUE #( FOR" and "LOOP AT": the difference is a memory allocation which is done only by "VALUE #( FOR" (STCK S 05), and also a little difference in TUTL which is accompanied by PAR1.

VALUE #( FOR:

  • iclr 04
  • STCK 05
  • iclr 00
  • STCK 04
  • ...
  • TUTL 1A
  • PAR1 10 

LOOP AT:

  • STCK 04
  • ...
  • TUTL 1A

I confirm that FILTER is fully implemented by kernel (only one instruction TOPS 10 with PAR1/PAR2), the loop is done by kernel, not by the byte code, it's why it's fast.

PS: for information, the byte code can be seen mainly via LOAD REPORT 'program name' PART 'CONT', but it's very difficult to decode it, and other "parts" are also needed to differentiate which code belongs to which method and so on.

Sandra_Rossi
Active Contributor

My conclusion concerning the performance between "VALUE #( FOR" and "LOOP AT": there's a constant overhead of 4 microseconds for "VALUE #( FOR" on my system (whatever the number of lines in the internal table). It's the same kind of meaningless difference as between LOOP AT ... ASSIGNING and LOOP AT ... REFERENCE INTO (styleguides/clean-abap/CleanABAP.md at main · SAP/styleguides · GitHub: "Additionally, data access via field symbols is slightly faster than data access via references. This is only noticable when loops make up a significant part of the runtime of the program and is often not relevant...")

BH91976
Explorer

Nice blog with accurate data. The only thing I find not correct is that the FOR loop is not debuggable. You can debug a FOR loop by changing the step size in your debugger. If you do that then you can see what is happening in the FOR loop.

BH91976_0-1745310320255.png

 

Michał_Biegun
Explorer
0 Kudos

@Sandra_Rossi  thank you for all the effort you put into extending my analysis.

I've found a small difference between our codes for LOOP. When you append or insert into selected_items, this table is a sorted one. This means, that with each step of the loop, the key has to be sorted. My version of selected_items is of type STANDARD, so inserting new records works faster. Probably that is why in your tests FILTER was so much better than LOOP.

This would suggest, that if you read from sorted table into sorted table, FILTER is always better.

Such a short source code and so many nuances...

 

Labels in this area