ABAP Tipp - Performance INSERT vs VALUE
In diesem Artikel schauen wir uns einmal die Performance der Einfüge-Operationen APPEND, INSERT und VALUE im Hinblick auf Tabellen an und bewerten die Performance und Stabilität bei der Entwicklung.
Inhaltsverzeichnis
Neben Append und Insert kannst du mittlerweile auch Value verwenden, um Zeilen an eine Tabelle zu hängen oder bei sortierten Tabellen einzufügen. Dazu wollen wir uns einmal die Performance beim Anfügen von Datensätzen anschauen und einen automatisierten Test machen. Zum Vergleich haben wir auch den "alten" Append aufgenommen, um hier vielleicht weitere Unterschiede zu ermitteln.
Aufbau
Für die Durchführung des Tests verwenden wir eine Console-Application und legen für jeden Testfall eine Methode an. Die Erzeugung und Haltung der Daten geschieht in der Methode, sodass nach verlassen der Methode der Garbage-Collector die alten Variablen aufräumen kann und so den Speicher freigibt. Die Laufzeitmessung geschieht außerhalb dieser Methode in MAIN. Für den Test haben wir jeweils eine kleine und eine große Tabelle vorgesehen, die jeweils eine unterschiedliche Komplexität darstellen.
TYPES:
BEGIN OF ts_small,
integer TYPE i,
text TYPE c LENGTH 35,
END OF ts_small,
tt_small TYPE STANDARD TABLE OF ts_small WITH EMPTY KEY,
tt_small_sorted TYPE SORTED TABLE OF ts_small WITH UNIQUE KEY integer,
BEGIN OF ts_big,
integer TYPE i,
text TYPE c LENGTH 35,
ltext TYPE string,
amt TYPE p LENGTH 15 DECIMALS 2,
curr TYPE waers,
tab TYPE string_table,
END OF ts_big,
tt_big TYPE STANDARD TABLE OF ts_big WITH EMPTY KEY,
tt_big_sorted TYPE SORTED TABLE OF ts_big WITH UNIQUE KEY integer.
Zur Erzeugung der einzelnen gefüllten Zeilen (Struktur), stellen wir zwei Hilfsmethoden zur Verfügung die uns unterschiedliche Zeilen an Hand des Index zur Verfügung stellen, sodass die eingefügten Daten nicht immer gleich sind. Die große Struktur besitzt zusätzlich noch eine tiefe Struktur, da wir eine String-Tabelle zur Verfügung stellen.
METHOD create_small_line.
rs_result-integer = id_index.
rs_result-text = |Text for number { id_index }|.
ENDMETHOD.
METHOD create_big_line.
rs_result-integer = id_index.
rs_result-text = |Text for number { id_index }|.
rs_result-ltext = |Some longtext with the number { id_index }. Add this to your table|.
rs_result-amt = CONV #( id_index ).
rs_result-curr = 'EUR'.
rs_result-tab = VALUE #( FOR ld_x = 1 THEN ld_x + 1 WHILE ld_x < 5 ( |Entry { ld_x }| ) ).
ENDMETHOD.
Für die Laufzeitmessung verwenden wir die Klasse CL_ABAP_RUNTIME die uns eine Startzeit zur Verfügung stellt und wir diese von den Endzeit subtrahieren können. Diesen Output geben wir dann direkt in die Console von Eclipse aus. An dieser Stelle hast du die Möglichkeit einen Timer mit hoher oder niedriger Genauigkeit zu generieren.
DATA(lo_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(ld_start) = lo_timer->get_runtime( ).
" Run test method
out->write( |TEST_METHOD_NAME: { lo_timer->get_runtime( ) - ld_start }| ).
Test
Die Tests führen wir entsprechend drei Mal durch und bestimmen den Mittelwert als Ergebnis für die Performance Messung. In den Screenshots könnt ihr die einzelnen Werte noch einmal im Detail sehen und wie sie sich für die entsprechenden Tabellengrößen verhalten.
Die Ergebnisse der Performance Messung im Durchschnitt sehen dann wie folgt aus. Die Werte wurden entsprechend in Sekunden umgerechnet, um leichter vergleichbar zu sein:
Art des Tests | APPEND | INSERT | VALUE |
---|---|---|---|
Standard Tabelle (klein) | 5,06 | 5,06 | 5,87 |
Standard Tabelle (groß) | 36,3 | 109,5 | 116,9 |
Sortierte Tabelle (klein) | 6,60 | 6,31 | 7,54 |
Sortierte Tabelle (groß) | 110,4 | 110,3 | 115,7 |
Der Value Befehl schneidet in fast allen Fällen etwas schlechter ab und weist eine schlechtere Performance gegenüber dem Insert auf. Zwischen Insert und Append gibt es kaum Unterschiede, außer wir betrachten uns die Zeit beim Anhängen von komplexen und großen Strukturen an eine Standard Tabelle. Hier kann Append mit mehr als 50% Laufzeitgewinn punkten und hängt die anderen beiden Statements ab. Bei der Verwendung einer sortierten Tabelle, kommt es aber kaum noch zu Abweichungen.
Hinweis: In unserem Beispiel verwenden wir einen APPEND auf eine sortierte Tabelle mit Schlüssel. Dies funktioniert nur, da der Schlüssel der Tabelle in aufsteigend sortierter Form angehangen wird (SY-INDEX). Für unser Beispiel ist es ausreichend, du solltest aber niemals einen Append an eine sortierte Tabelle vornehmen, da es sonst zu einer Ausnahme (Schlüsselverletzung) kommt.
Beispiel
Hier noch einmal die vollständige Klasse zur Nachstellung des Tests. Über die Konstante C_LOOP_VALUE kann die Anzahl der erzeugten Datensätze reguliert werden. Bei ca. 7,5 Millionen Einträgen in der Testtabelle ging dem Prozess aber auf unserem System der Speicher aus und es kam zu einem Abbruch, daher haben wir den Wert auf 5 Millionen gestellt.
CLASS zcl_test_table_performance DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
CONSTANTS:
c_loop_value TYPE i VALUE 5000000.
TYPES:
BEGIN OF ts_small,
integer TYPE i,
text TYPE c LENGTH 35,
END OF ts_small,
tt_small TYPE STANDARD TABLE OF ts_small WITH EMPTY KEY,
tt_small_sorted TYPE SORTED TABLE OF ts_small WITH UNIQUE KEY integer,
BEGIN OF ts_big,
integer TYPE i,
text TYPE c LENGTH 35,
ltext TYPE string,
amt TYPE p LENGTH 15 DECIMALS 2,
curr TYPE waers,
tab TYPE string_table,
END OF ts_big,
tt_big TYPE STANDARD TABLE OF ts_big WITH EMPTY KEY,
tt_big_sorted TYPE SORTED TABLE OF ts_big WITH UNIQUE KEY integer.
METHODS:
create_small_line
IMPORTING
id_index TYPE i
RETURNING VALUE(rs_result) TYPE ts_small,
create_big_line
IMPORTING
id_index TYPE i
RETURNING VALUE(rs_result) TYPE ts_big,
test_append_small,
test_append_big,
test_insert_small,
test_insert_big,
test_value_small,
test_value_big,
sort_append_small,
sort_append_big,
sort_insert_small,
sort_insert_big,
sort_value_small,
sort_value_big.
ENDCLASS.
CLASS zcl_test_table_performance IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lo_timer) = cl_abap_runtime=>create_hr_timer( ).
DATA(ld_start) = lo_timer->get_runtime( ).
test_append_small( ).
out->write( |TEST_APPEND_SMALL: { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
test_insert_small( ).
out->write( |TEST_INSERT_SMALL: { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
test_value_small( ).
out->write( |TEST_VALUE_SMALL : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
test_append_big( ).
out->write( |TEST_APPEND_BIG : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
test_insert_big( ).
out->write( |TEST_INSERT_BIG : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
test_value_big( ).
out->write( |TEST_VALUE_BIG : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
sort_append_small( ).
out->write( |SORT_APPEND_SMALL: { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
sort_insert_small( ).
out->write( |SORT_INSERT_SMALL: { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
sort_value_small( ).
out->write( |SORT_VALUE_SMALL : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
sort_append_big( ).
out->write( |SORT_APPEND_BIG : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
sort_insert_big( ).
out->write( |SORT_INSERT_BIG : { lo_timer->get_runtime( ) - ld_start }| ).
ld_start = lo_timer->get_runtime( ).
sort_value_big( ).
out->write( |SORT_VALUE_BIG : { lo_timer->get_runtime( ) - ld_start }| ).
ENDMETHOD.
METHOD create_small_line.
rs_result-integer = id_index.
rs_result-text = |Text for number { id_index }|.
ENDMETHOD.
METHOD create_big_line.
rs_result-integer = id_index.
rs_result-text = |Text for number { id_index }|.
rs_result-ltext = |Some longtext with the number { id_index }. Add this to your table|.
rs_result-amt = CONV #( id_index ).
rs_result-curr = 'EUR'.
rs_result-tab = VALUE #( FOR ld_x = 1 THEN ld_x + 1 WHILE ld_x < 5 ( |Entry { ld_x }| ) ).
ENDMETHOD.
METHOD test_append_small.
DATA:
lt_table TYPE tt_small.
DO c_loop_value TIMES.
DATA(ls_line) = create_small_line( sy-index ).
APPEND ls_line TO lt_table.
ENDDO.
ENDMETHOD.
METHOD test_append_big.
DATA:
lt_table TYPE tt_big.
DO c_loop_value TIMES.
DATA(ls_line) = create_big_line( sy-index ).
APPEND ls_line TO lt_table.
ENDDO.
ENDMETHOD.
METHOD test_insert_small.
DATA:
lt_table TYPE tt_small.
DO c_loop_value TIMES.
DATA(ls_line) = create_small_line( sy-index ).
INSERT ls_line INTO TABLE lt_table.
ENDDO.
ENDMETHOD.
METHOD test_insert_big.
DATA:
lt_table TYPE tt_big.
DO c_loop_value TIMES.
DATA(ls_line) = create_big_line( sy-index ).
INSERT ls_line INTO TABLE lt_table.
ENDDO.
ENDMETHOD.
METHOD test_value_small.
DATA:
lt_table TYPE tt_small.
DO c_loop_value TIMES.
DATA(ls_line) = create_small_line( sy-index ).
lt_table = VALUE #( BASE lt_table ( integer = ls_line-integer text = ls_line-text ) ).
ENDDO.
ENDMETHOD.
METHOD test_value_big.
DATA:
lt_table TYPE tt_big.
DO c_loop_value TIMES.
DATA(ls_line) = create_big_line( sy-index ).
lt_table = VALUE #( BASE lt_table (
integer = ls_line-integer
text = ls_line-text
ltext = ls_line-ltext
amt = ls_line-amt
curr = ls_line-curr
tab = ls_line-tab
) ).
ENDDO.
ENDMETHOD.
METHOD sort_append_small.
DATA:
lt_table TYPE tt_small_sorted.
DO c_loop_value TIMES.
DATA(ls_line) = create_small_line( sy-index ).
APPEND ls_line TO lt_table.
ENDDO.
ENDMETHOD.
METHOD sort_append_big.
DATA:
lt_table TYPE tt_big_sorted.
DO c_loop_value TIMES.
DATA(ls_line) = create_big_line( sy-index ).
APPEND ls_line TO lt_table.
ENDDO.
ENDMETHOD.
METHOD sort_insert_small.
DATA:
lt_table TYPE tt_small_sorted.
DO c_loop_value TIMES.
DATA(ls_line) = create_small_line( sy-index ).
INSERT ls_line INTO TABLE lt_table.
ENDDO.
ENDMETHOD.
METHOD sort_insert_big.
DATA:
lt_table TYPE tt_big_sorted.
DO c_loop_value TIMES.
DATA(ls_line) = create_big_line( sy-index ).
INSERT ls_line INTO TABLE lt_table.
ENDDO.
ENDMETHOD.
METHOD sort_value_small.
DATA:
lt_table TYPE tt_small_sorted.
DO c_loop_value TIMES.
DATA(ls_line) = create_small_line( sy-index ).
lt_table = VALUE #( BASE lt_table ( integer = ls_line-integer text = ls_line-text ) ).
ENDDO.
ENDMETHOD.
METHOD sort_value_big.
DATA:
lt_table TYPE tt_big_sorted.
DO c_loop_value TIMES.
DATA(ls_line) = create_big_line( sy-index ).
lt_table = VALUE #( BASE lt_table (
integer = ls_line-integer
text = ls_line-text
ltext = ls_line-ltext
amt = ls_line-amt
curr = ls_line-curr
tab = ls_line-tab
) ).
ENDDO.
ENDMETHOD.
ENDCLASS.
Fazit
Der VALUE Befehl ist zwar sehr umfangreich und bietet viele neue Features, doch kann man bei der Übernahme der Datensätze weiterhin getrost INSERT verwenden, um auch auf der sicheren und stabilen Seite zu bleiben und den Performancevorteil mitnehmen. Von APPEND raten wir mittlerweile grundsätzlich ab, da sich im Laufe der Entwicklung der Tabellentyp verändern kann und man sich somit nachträgliche Änderungen am Quellcode spart. Mehr dazu erfährst du in einem anderen Artikel von uns.