ABAP Performance-Optimierung: Schnellere Programme entwickeln

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Performance-Optimierung ist entscheidend für benutzerfreundliche und ressourcenschonende ABAP-Programme. Dieser Artikel zeigt die wichtigsten Techniken zur Leistungssteigerung.

Performance-Analyse-Tools

ToolTransaktionBeschreibung
SQL TraceST05Datenbankzugriffe analysieren
ABAP TraceSATLaufzeitanalyse
Code InspectorSCIStatische Codeanalyse
ABAP ProfilerSATDetaillierte Profilerstellung
Explain PlanST05SQL-Ausführungsplan

SQL-Optimierung

Nur benötigte Spalten lesen

" Schlecht - alle Spalten lesen
SELECT * FROM mara INTO TABLE @DATA(lt_mara)
WHERE mtart = 'FERT'.
" Gut - nur benötigte Spalten
SELECT matnr, maktx, mtart, matkl
FROM mara
INTO TABLE @DATA(lt_mara_opt)
WHERE mtart = 'FERT'.

SELECT mit Index nutzen

" Schlecht - kein Index nutzbar
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr.
" Gut - Primärindex nutzen
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE vbeln = @lv_vbeln
AND posnr = @lv_posnr.
" Gut - Sekundärindex nutzen (wenn vorhanden)
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr
%_HINTS ORACLE 'INDEX(VBAP VBAP~Z01)'.

FOR ALL ENTRIES richtig nutzen

" Prüfung auf leere Tabelle ist PFLICHT!
IF lt_orders IS NOT INITIAL.
SELECT vbeln, posnr, matnr, kwmeng
FROM vbap
FOR ALL ENTRIES IN @lt_orders
WHERE vbeln = @lt_orders-vbeln
INTO TABLE @DATA(lt_items).
ENDIF.
" Alternative: JOIN statt FOR ALL ENTRIES
SELECT v~vbeln, v~posnr, v~matnr, v~kwmeng
FROM vbap AS v
INNER JOIN @lt_orders AS o ON v~vbeln = o~vbeln
INTO TABLE @DATA(lt_items_join).

Aggregatfunktionen nutzen

" Schlecht - alle Daten lesen und summieren
SELECT * FROM vbap INTO TABLE @DATA(lt_all)
WHERE vbeln = @lv_vbeln.
DATA(lv_sum) = REDUCE kwmeng( INIT sum = 0
FOR wa IN lt_all
NEXT sum = sum + wa-kwmeng ).
" Gut - Datenbank summiert
SELECT SUM( kwmeng ) AS total
FROM vbap
WHERE vbeln = @lv_vbeln
INTO @DATA(lv_sum_db).

Pufferung aktivieren

" Tabellenpufferung in SE11 aktivieren:
" - Vollständige Pufferung (kleine Tabellen)
" - Generische Pufferung (Teil der Schlüsselfelder)
" - Einzelsatzpufferung
" Puffer umgehen bei Bedarf
SELECT SINGLE * FROM t001 BYPASSING BUFFER
INTO @DATA(ls_t001)
WHERE bukrs = @lv_bukrs.

Bulk-Operationen

" Schlecht - einzelne INSERTs
LOOP AT lt_new_data INTO DATA(ls_data).
INSERT ztable FROM ls_data.
ENDLOOP.
" Gut - Array INSERT
INSERT ztable FROM TABLE lt_new_data.
" Gut - Array UPDATE
UPDATE ztable FROM TABLE lt_update_data.
" Gut - Array DELETE
DELETE ztable FROM TABLE lt_delete_data.

Interne Tabellen

Richtige Tabellenart wählen

" STANDARD TABLE - sequentieller Zugriff, APPEND
DATA: lt_standard TYPE STANDARD TABLE OF sflight.
" SORTED TABLE - binäre Suche, sortierter Zugriff
DATA: lt_sorted TYPE SORTED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.
" HASHED TABLE - direkter Schlüsselzugriff
DATA: lt_hashed TYPE HASHED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.

READ TABLE optimieren

" Schlecht - lineare Suche
READ TABLE lt_standard INTO DATA(ls_line)
WITH KEY carrid = 'LH'.
" Gut - binäre Suche (Tabelle muss sortiert sein!)
SORT lt_standard BY carrid.
READ TABLE lt_standard INTO ls_line
WITH KEY carrid = 'LH' BINARY SEARCH.
" Gut - SORTED oder HASHED TABLE
READ TABLE lt_sorted INTO ls_line
WITH TABLE KEY carrid = 'LH' connid = '0400' fldate = '20250115'.

Sekundärschlüssel nutzen

" Sekundärschlüssel definieren
DATA: lt_flights TYPE SORTED TABLE OF sflight
WITH UNIQUE KEY primary_key COMPONENTS carrid connid fldate
WITH NON-UNIQUE SORTED KEY by_plane COMPONENTS planetype.
" Zugriff über Sekundärschlüssel
READ TABLE lt_flights INTO DATA(ls_flight)
WITH KEY by_plane COMPONENTS planetype = 'A380'.
LOOP AT lt_flights INTO ls_flight USING KEY by_plane
WHERE planetype = 'A380'.
ENDLOOP.

LOOP-Optimierung

" Schlecht - WHERE ohne Index
LOOP AT lt_large_table INTO DATA(ls_line)
WHERE status = 'A'.
ENDLOOP.
" Gut - WHERE auf sortierte Tabelle
LOOP AT lt_sorted INTO ls_line
WHERE carrid = 'LH'. " Nutzt Sortierung
ENDLOOP.
" Gut - ASSIGNING statt INTO (keine Kopie)
LOOP AT lt_table ASSIGNING FIELD-SYMBOL(<ls_line>).
<ls_line>-status = 'P'. " Direkte Änderung
ENDLOOP.
" Gut - REFERENCE INTO für Leseoperationen
LOOP AT lt_table REFERENCE INTO DATA(lr_line).
DATA(lv_value) = lr_line->field.
ENDLOOP.

Tabellen-Joins im Speicher

" Schlecht - verschachtelte LOOPs
LOOP AT lt_orders INTO DATA(ls_order).
LOOP AT lt_items INTO DATA(ls_item)
WHERE order_id = ls_order-order_id.
ENDLOOP.
ENDLOOP.
" Gut - SORTED TABLE mit WHERE
DATA: lt_items_sorted TYPE SORTED TABLE OF ty_item
WITH NON-UNIQUE KEY order_id.
lt_items_sorted = lt_items.
LOOP AT lt_orders INTO ls_order.
LOOP AT lt_items_sorted INTO ls_item
WHERE order_id = ls_order-order_id.
ENDLOOP.
ENDLOOP.

Parallelisierung

Parallele RFC-Aufrufe

DATA: lv_task TYPE string,
lv_counter TYPE i,
lt_results TYPE TABLE OF ty_result.
" Parallele Tasks starten
LOOP AT lt_work_packages INTO DATA(ls_package).
lv_counter = lv_counter + 1.
lv_task = |TASK{ lv_counter }|.
CALL FUNCTION 'Z_PROCESS_PACKAGE'
STARTING NEW TASK lv_task
DESTINATION IN GROUP DEFAULT
CALLING on_task_complete ON END OF TASK
EXPORTING
is_package = ls_package
EXCEPTIONS
resource_failure = 1
communication_failure = 2.
IF sy-subrc <> 0.
" Fallback: synchron verarbeiten
CALL FUNCTION 'Z_PROCESS_PACKAGE'
EXPORTING
is_package = ls_package
IMPORTING
es_result = DATA(ls_result).
APPEND ls_result TO lt_results.
ENDIF.
ENDLOOP.
" Auf alle Tasks warten
WAIT UNTIL lv_completed = lv_counter UP TO 300 SECONDS.
" Callback-Methode
FORM on_task_complete USING p_task TYPE clike.
DATA: ls_result TYPE ty_result.
RECEIVE RESULTS FROM FUNCTION 'Z_PROCESS_PACKAGE'
IMPORTING
es_result = ls_result.
APPEND ls_result TO lt_results.
lv_completed = lv_completed + 1.
ENDFORM.

SPTA Framework

" SPTA für parallele Verarbeitung
DATA: lt_input TYPE spta_t_input,
lt_output TYPE spta_t_output.
" Input vorbereiten
LOOP AT lt_work_items INTO DATA(ls_item).
APPEND INITIAL LINE TO lt_input ASSIGNING FIELD-SYMBOL(<ls_input>).
<ls_input>-data = ls_item.
ENDLOOP.
" Parallel verarbeiten
CALL FUNCTION 'SPTA_PARA_PROCESS_START_2'
EXPORTING
server_group = 'parallel_generators'
max_no_of_tasks = 10
before_rfc_callback = 'PREPARE_PACKAGE'
in_rfc_callback = 'PROCESS_PACKAGE'
after_rfc_callback = 'COLLECT_RESULT'
CHANGING
user_param = lt_input
EXCEPTIONS
OTHERS = 1.

String-Operationen

Effiziente Verkettung

" Schlecht - wiederholte CONCATENATE
DATA: lv_result TYPE string.
LOOP AT lt_parts INTO DATA(lv_part).
CONCATENATE lv_result lv_part INTO lv_result.
ENDLOOP.
" Gut - String Templates
lv_result = REDUCE string( INIT r = ``
FOR part IN lt_parts
NEXT r = r && part ).
" Gut - CONCATENATE LINES OF
CONCATENATE LINES OF lt_parts INTO lv_result SEPARATED BY space.

Reguläre Ausdrücke cachen

" Schlecht - Regex bei jedem Aufruf neu kompilieren
LOOP AT lt_strings INTO DATA(lv_string).
FIND REGEX '\d{4}-\d{2}-\d{2}' IN lv_string.
ENDLOOP.
" Gut - Regex einmal kompilieren
DATA(lo_regex) = NEW cl_abap_regex( pattern = '\d{4}-\d{2}-\d{2}' ).
DATA(lo_matcher) = lo_regex->create_matcher( text = `` ).
LOOP AT lt_strings INTO lv_string.
lo_matcher->match( text = lv_string ).
ENDLOOP.

Speicheroptimierung

Große Tabellen freigeben

" Speicher explizit freigeben
CLEAR lt_large_table.
FREE lt_large_table.
" Oder mit REDUCE verarbeiten
DATA(lv_sum) = REDUCE i( INIT s = 0
FOR <line> IN lt_large_table
NEXT s = s + <line>-amount ).
FREE lt_large_table. " Sofort freigeben

Paketweise Verarbeitung

DATA: lv_offset TYPE i VALUE 0,
lv_limit TYPE i VALUE 1000.
DO.
SELECT * FROM large_table
ORDER BY primary_key
INTO TABLE @DATA(lt_package)
OFFSET @lv_offset
UP TO @lv_limit ROWS.
IF lt_package IS INITIAL.
EXIT.
ENDIF.
" Paket verarbeiten
LOOP AT lt_package INTO DATA(ls_line).
" Verarbeitung...
ENDLOOP.
FREE lt_package.
lv_offset = lv_offset + lv_limit.
ENDDO.

Pufferung

Shared Memory Objects

" Shared Memory für häufig gelesene Daten
CLASS zcl_cache DEFINITION.
PUBLIC SECTION.
CLASS-METHODS get_config
RETURNING VALUE(rt_config) TYPE ty_config_tab.
ENDCLASS.
CLASS zcl_cache IMPLEMENTATION.
METHOD get_config.
" Shared Buffer prüfen
IMPORT config = rt_config FROM SHARED BUFFER indx(zc)
ID 'CONFIG'.
IF sy-subrc <> 0.
" Daten laden
SELECT * FROM zconfig INTO TABLE rt_config.
" Im Shared Buffer speichern
EXPORT config = rt_config TO SHARED BUFFER indx(zc)
ID 'CONFIG'.
ENDIF.
ENDMETHOD.
ENDCLASS.

ABAP Shared Objects

" Shared Objects für komplexe Daten
" 1. Area definieren (SHMA)
" 2. Root-Klasse erstellen
TRY.
" Area Handle holen
DATA(lo_handle) = zcl_my_area=>attach_for_read( ).
DATA(lo_root) = lo_handle->get_root( ).
" Daten lesen
DATA(lt_data) = lo_root->get_cached_data( ).
lo_handle->detach( ).
CATCH cx_shm_error.
" Fallback
ENDTRY.

Performance-Analyse

SQL Trace auswerten

" ST05 aktivieren
" Programm ausführen
" Trace auswerten
" Typische Probleme:
" - Identical Selects (gleiche Abfrage mehrfach)
" - SELECT * (zu viele Spalten)
" - Fehlender Index
" - Zu viele Einzelsätze

Runtime Analysis (SAT)

" SAT Trace erstellen
SET RUN TIME ANALYZER ON.
" ... Code ausführen ...
SET RUN TIME ANALYZER OFF.
" Oder per Transaktion SAT
" - Aggregierte Ansicht
" - Hit List
" - Call Hierarchy

Code Inspector Checks

" SCI - wichtige Check-Varianten:
" - SELECT in LOOP
" - APPEND in LOOP statt Array
" - Fehlende WHERE-Bedingung
" - INTO CORRESPONDING FIELDS
" - Ineffiziente Tabellenoperationen

Messung und Benchmarking

DATA: lv_start TYPE i,
lv_end TYPE i,
lv_diff TYPE i.
GET RUN TIME FIELD lv_start.
" Code messen
LOOP AT lt_table INTO DATA(ls_line).
" ...
ENDLOOP.
GET RUN TIME FIELD lv_end.
lv_diff = lv_end - lv_start.
WRITE: / 'Laufzeit:', lv_diff, 'Mikrosekunden'.
" Oder mit Timestamps
DATA(lv_ts_start) = utclong_current( ).
" ... Code ...
DATA(lv_ts_end) = utclong_current( ).
DATA(lv_seconds) = cl_abap_tstmp=>subtract(
tstmp1 = lv_ts_end
tstmp2 = lv_ts_start ).

Best Practices

  1. Messen vor Optimieren: Immer erst profilen, dann optimieren
  2. Datenbank-Last reduzieren: Weniger SELECTs, effiziente WHERE
  3. Richtige Tabellenart: HASHED für Key-Zugriff, SORTED für Bereiche
  4. Speicher freigeben: FREE für große Tabellen nach Nutzung
  5. Parallelisierung: Bei unabhängigen Aufgaben
  6. Pufferung: Für häufig gelesene, selten geänderte Daten

Wichtige Transaktionen

TransaktionBeschreibung
ST05SQL/Buffer Trace
SATABAP Trace
SCICode Inspector
ST12Kombinierter Trace
SM50Workprozesse
ST02Pufferstatistik

Verwandte Themen