ABAP Performance Optimization: Developing Faster Programs

Category
ABAP-Statements
Published
Author
Johannes

Performance optimization is crucial for user-friendly and resource-efficient ABAP programs. This article shows the most important techniques for improving performance.

Performance Analysis Tools

ToolTransactionDescription
SQL TraceST05Analyze database accesses
ABAP TraceSATRuntime analysis
Code InspectorSCIStatic code analysis
ABAP ProfilerSATDetailed profiling
Explain PlanST05SQL execution plan

SQL Optimization

Only Read Required Columns

" Bad - read all columns
SELECT * FROM mara INTO TABLE @DATA(lt_mara)
WHERE mtart = 'FERT'.
" Good - only required columns
SELECT matnr, maktx, mtart, matkl
FROM mara
INTO TABLE @DATA(lt_mara_opt)
WHERE mtart = 'FERT'.

SELECT with Index Usage

" Bad - no index usable
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr.
" Good - use primary index
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE vbeln = @lv_vbeln
AND posnr = @lv_posnr.
" Good - use secondary index (if available)
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr
%_HINTS ORACLE 'INDEX(VBAP VBAP~Z01)'.

Use FOR ALL ENTRIES Correctly

" Check for empty table is MANDATORY!
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 instead of 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).

Use Aggregate Functions

" Bad - read all data and sum
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 ).
" Good - database sums
SELECT SUM( kwmeng ) AS total
FROM vbap
WHERE vbeln = @lv_vbeln
INTO @DATA(lv_sum_db).

Enable Buffering

" Enable table buffering in SE11:
" - Full buffering (small tables)
" - Generic buffering (part of key fields)
" - Single record buffering
" Bypass buffer when needed
SELECT SINGLE * FROM t001 BYPASSING BUFFER
INTO @DATA(ls_t001)
WHERE bukrs = @lv_bukrs.

Bulk Operations

" Bad - individual INSERTs
LOOP AT lt_new_data INTO DATA(ls_data).
INSERT ztable FROM ls_data.
ENDLOOP.
" Good - array INSERT
INSERT ztable FROM TABLE lt_new_data.
" Good - array UPDATE
UPDATE ztable FROM TABLE lt_update_data.
" Good - array DELETE
DELETE ztable FROM TABLE lt_delete_data.

Internal Tables

Choose the Right Table Type

" STANDARD TABLE - sequential access, APPEND
DATA: lt_standard TYPE STANDARD TABLE OF sflight.
" SORTED TABLE - binary search, sorted access
DATA: lt_sorted TYPE SORTED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.
" HASHED TABLE - direct key access
DATA: lt_hashed TYPE HASHED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.

Optimize READ TABLE

" Bad - linear search
READ TABLE lt_standard INTO DATA(ls_line)
WITH KEY carrid = 'LH'.
" Good - binary search (table must be sorted!)
SORT lt_standard BY carrid.
READ TABLE lt_standard INTO ls_line
WITH KEY carrid = 'LH' BINARY SEARCH.
" Good - SORTED or HASHED TABLE
READ TABLE lt_sorted INTO ls_line
WITH TABLE KEY carrid = 'LH' connid = '0400' fldate = '20250115'.

Use Secondary Keys

" Define secondary key
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.
" Access via secondary key
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 Optimization

" Bad - WHERE without index
LOOP AT lt_large_table INTO DATA(ls_line)
WHERE status = 'A'.
ENDLOOP.
" Good - WHERE on sorted table
LOOP AT lt_sorted INTO ls_line
WHERE carrid = 'LH'. " Uses sorting
ENDLOOP.
" Good - ASSIGNING instead of INTO (no copy)
LOOP AT lt_table ASSIGNING FIELD-SYMBOL(<ls_line>).
<ls_line>-status = 'P'. " Direct modification
ENDLOOP.
" Good - REFERENCE INTO for read operations
LOOP AT lt_table REFERENCE INTO DATA(lr_line).
DATA(lv_value) = lr_line->field.
ENDLOOP.

Table Joins in Memory

" Bad - nested 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.
" Good - SORTED TABLE with 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.

Parallelization

Parallel RFC Calls

DATA: lv_task TYPE string,
lv_counter TYPE i,
lt_results TYPE TABLE OF ty_result.
" Start parallel tasks
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: process synchronously
CALL FUNCTION 'Z_PROCESS_PACKAGE'
EXPORTING
is_package = ls_package
IMPORTING
es_result = DATA(ls_result).
APPEND ls_result TO lt_results.
ENDIF.
ENDLOOP.
" Wait for all tasks
WAIT UNTIL lv_completed = lv_counter UP TO 300 SECONDS.
" Callback method
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 for parallel processing
DATA: lt_input TYPE spta_t_input,
lt_output TYPE spta_t_output.
" Prepare input
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.
" Process in parallel
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 Operations

Efficient Concatenation

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

Cache Regular Expressions

" Bad - recompile regex on every call
LOOP AT lt_strings INTO DATA(lv_string).
FIND REGEX '\d{4}-\d{2}-\d{2}' IN lv_string.
ENDLOOP.
" Good - compile regex once
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.

Memory Optimization

Free Large Tables

" Explicitly free memory
CLEAR lt_large_table.
FREE lt_large_table.
" Or process with REDUCE
DATA(lv_sum) = REDUCE i( INIT s = 0
FOR <line> IN lt_large_table
NEXT s = s + <line>-amount ).
FREE lt_large_table. " Free immediately

Package-wise Processing

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.
" Process package
LOOP AT lt_package INTO DATA(ls_line).
" Processing...
ENDLOOP.
FREE lt_package.
lv_offset = lv_offset + lv_limit.
ENDDO.

Buffering

Shared Memory Objects

" Shared memory for frequently read data
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.
" Check shared buffer
IMPORT config = rt_config FROM SHARED BUFFER indx(zc)
ID 'CONFIG'.
IF sy-subrc <> 0.
" Load data
SELECT * FROM zconfig INTO TABLE rt_config.
" Store in shared buffer
EXPORT config = rt_config TO SHARED BUFFER indx(zc)
ID 'CONFIG'.
ENDIF.
ENDMETHOD.
ENDCLASS.

ABAP Shared Objects

" Shared objects for complex data
" 1. Define area (SHMA)
" 2. Create root class
TRY.
" Get area handle
DATA(lo_handle) = zcl_my_area=>attach_for_read( ).
DATA(lo_root) = lo_handle->get_root( ).
" Read data
DATA(lt_data) = lo_root->get_cached_data( ).
lo_handle->detach( ).
CATCH cx_shm_error.
" Fallback
ENDTRY.

Performance Analysis

Evaluate SQL Trace

" Activate ST05
" Run program
" Evaluate trace
" Typical problems:
" - Identical Selects (same query multiple times)
" - SELECT * (too many columns)
" - Missing index
" - Too many single records

Runtime Analysis (SAT)

" Create SAT trace
SET RUN TIME ANALYZER ON.
" ... execute code ...
SET RUN TIME ANALYZER OFF.
" Or via transaction SAT
" - Aggregated view
" - Hit List
" - Call Hierarchy

Code Inspector Checks

" SCI - important check variants:
" - SELECT in LOOP
" - APPEND in LOOP instead of array
" - Missing WHERE condition
" - INTO CORRESPONDING FIELDS
" - Inefficient table operations

Measurement and Benchmarking

DATA: lv_start TYPE i,
lv_end TYPE i,
lv_diff TYPE i.
GET RUN TIME FIELD lv_start.
" Measure code
LOOP AT lt_table INTO DATA(ls_line).
" ...
ENDLOOP.
GET RUN TIME FIELD lv_end.
lv_diff = lv_end - lv_start.
WRITE: / 'Runtime:', lv_diff, 'microseconds'.
" Or with 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. Measure before optimizing: Always profile first, then optimize
  2. Reduce database load: Fewer SELECTs, efficient WHERE
  3. Right table type: HASHED for key access, SORTED for ranges
  4. Free memory: FREE for large tables after use
  5. Parallelization: For independent tasks
  6. Buffering: For frequently read, rarely changed data

Important Transactions

TransactionDescription
ST05SQL/Buffer Trace
SATABAP Trace
SCICode Inspector
ST12Combined Trace
SM50Work Processes
ST02Buffer Statistics