Перейти до вмісту

Продуктивність

Три рівні, де зазвичай втрачається продуктивність ABAP-програми: доступ до бази, обробка внутрішніх таблиць, і інше (строки, цикли, конверсії). Правило номер один — не читай з БД більше ніж треба, і не читай двічі. Далі — вибрати правильний table category й уникати непотрібних копіювань.

Ця сторінка — практичні ідіоми, які дають найбільший ефект. Вимірювати реальний перфоманс — через SAT (transaction) або cl_abap_runtime.

Завжди фільтруй на стороні БД через WHERE, а не після вибірки в ABAP:

" Добре — БД повертає тільки потрібні рядки
SELECT key_field, char1, char2, num1, num2
FROM zdemo_abap_tab1
WHERE key_field < 500
INTO TABLE @DATA(it1).
" Погано — всі рядки летять через мережу, потім чистимо в памʼяті
SELECT key_field, char1, char2, num1, num2
FROM zdemo_abap_tab1
INTO TABLE @DATA(it2).
LOOP AT it2 REFERENCE INTO DATA(dref).
IF dref->key_field < 500.
DELETE it2 INDEX sy-tabix.
ENDIF.
ENDLOOP.

Також:

  • SELECT SINGLE — коли треба один рядок.
  • UP TO n ROWS — коли достатньо перших n.
  • DISTINCT — якщо дублі не потрібні.

Не SELECT *, а конкретні поля. Колонковий SAP HANA тим більше любить вузькі селекти:

" Добре
SELECT carrid, connid, cityfrom, countryfr, cityto, countryto
FROM zdemo_abap_flsch
INTO TABLE @DATA(it7).
" Погано — тягнемо всі стовпці, включно з рідко потрібними
SELECT *
FROM zdemo_abap_flsch
INTO TABLE @DATA(it8).

Для UPDATE — те саме: SET col = ..., не UPDATE FROM @wa з усіма полями.

Замість двох запитів — join:

SELECT fl~carrid, carr~carrname, fl~connid, fl~cityfrom, fl~cityto
FROM zdemo_abap_flsch AS fl
LEFT OUTER JOIN zdemo_abap_carr AS carr
ON fl~carrid = carr~carrid
INTO TABLE @DATA(it).

Замість двох запитів — subquery або join з internal table:

" Підтягуємо тільки ті, чий ключ є у key_tab
SELECT key_field, char1, char2, num1, num2
FROM zdemo_abap_tab1
INNER JOIN @key_tab AS tab
ON zdemo_abap_tab1~key_field = tab~table_line
INTO CORRESPONDING FIELDS OF TABLE @itab1.
" Альтернатива з EXISTS
SELECT key_field, char1, char2, num1, num2
FROM zdemo_abap_tab1
WHERE EXISTS ( SELECT 'X' FROM @key_tab AS tab
WHERE zdemo_abap_tab1~key_field = tab~table_line )
INTO CORRESPONDING FIELDS OF TABLE @itab1.

Один INSERT/UPDATE/DELETE з внутрішньої таблиці — завжди швидше за цикл з одиничними операціями:

" Одна команда — один database round-trip
INSERT zdemo_tab FROM TABLE @itab.
" Погано — N round-trips
LOOP AT itab INTO DATA(wa).
INSERT zdemo_tab FROM wa.
ENDLOOP.
ОпераціяSTANDARDSORTEDHASHED
доступ за indexO(1)O(1)
пошук за повним keyO(n)O(log n)O(1)
пошук за префіксом keyO(n)O(log n)
вставка в кінецьO(1)
вставка за keyO(log n)O(1)

Правило: часто шукаєш за ключем на великій таблиці — HASHED або SORTED. Часто додаєш у кінець і читаєш послідовно — STANDARD. Великий обʼєм + рідкі модифікації + багато читань — SORTED/HASHED з secondary key.

" O(1) — хеш-таблиця
READ TABLE hashed_tab TRANSPORTING NO FIELDS
WITH TABLE KEY comp1 = sy-index.
" O(log n) — сортована
READ TABLE sorted_tab TRANSPORTING NO FIELDS
WITH TABLE KEY comp1 = sy-index.
" O(n) — STANDARD без secondary key, лінійний пошук
READ TABLE standard_tab TRANSPORTING NO FIELDS
WITH TABLE KEY comp1 = sy-index.

TRANSPORTING NO FIELDS — коли треба лише дізнатись наявність (перевірка sy-subrc), не копіювати весь рядок у work area.

WHERE у LOOP AT використовує ключ, якщо поля входять у ключ таблиці:

" Повільно — STANDARD таблиця, лінійний прохід
LOOP AT standard_tab REFERENCE INTO DATA(dref)
WHERE comp1 = 800 AND comp2 = '800' AND comp3 = '800'.
...
ENDLOOP.
" Швидше — SORTED з ключем на (comp1, comp2, comp3)
LOOP AT sorted_tab_mult_key_comp REFERENCE INTO DATA(dref)
WHERE comp1 = 800 AND comp2 = '800' AND comp3 = '800'.
...
ENDLOOP.

Великі (deep) структури — передавай лише ті поля, що справді міняєш:

" Добре — копіюємо тільки comp6
LOOP AT deep_standard_tab INTO DATA(wa)
WHERE comp1 < 400.
wa-comp6 += 1.
MODIFY deep_standard_tab FROM wa TRANSPORTING comp6.
ENDLOOP.
" Погано — вся структура переписується
LOOP AT deep_standard_tab INTO DATA(wa)
WHERE comp1 < 400.
wa-comp6 += 1.
MODIFY deep_standard_tab FROM wa.
ENDLOOP.

Ще швидше — ASSIGNING FIELD-SYMBOL(<fs>) і писати <fs>-comp6 += 1 напряму, без MODIFY взагалі.

" Один переніс всіх рядків
APPEND LINES OF str_tab_a TO str_tab_b.
" Цикл з порядковим копіюванням — повільніше, бо APPEND + копія кожного рядка
LOOP AT str_tab_a INTO DATA(wa).
APPEND wa TO str_tab_b.
ENDLOOP.

Великі STANDARD/SORTED таблиці, що часто читаються за різними ключами, — додай secondary key замість повторних сортувань:

DATA standard_tab_w_sec_key TYPE STANDARD TABLE OF ty_line
WITH DEFAULT KEY
WITH NON-UNIQUE SORTED KEY sec_key COMPONENTS comp1.
READ TABLE standard_tab_w_sec_key TRANSPORTING NO FIELDS
WITH TABLE KEY sec_key COMPONENTS comp1 = sy-index.

Коштує додаткової пам’яті і часу на оновлення секондарі при зміні таблиці — окупається тільки при багатьох читаннях.

" Добре — явно вказано поле
SORT standard_tab_w_default_key BY comp1 ASCENDING.
" Погано — сортує за default key, який невідомо з чого складається
SORT standard_tab_w_default_key.
CLEAR itab. " скидає вміст, памʼять лишається зарезервована
FREE itab. " звільняє памʼять повністю

FREE має сенс, коли таблиця була великою і більше не буде наповнюватися так само.

WHILE зазвичай трохи швидший за DO + EXIT:

" Швидше
WHILE num <= 1000.
num += 1.
ENDWHILE.
" Повільніше
DO.
num += 1.
IF sy-index = 1000.
EXIT.
ENDIF.
ENDDO.

Для змінних за довжиною — string швидший за c LENGTH n, бо не потребує повної перезаписи буфера:

DATA: str_a TYPE string,
str_b TYPE string VALUE '#'.
str_a &&= str_b. " швидше
DATA: char_a TYPE c LENGTH 1000,
char_b TYPE c LENGTH 1 VALUE '#'.
char_a &&= char_b. " повільніше — повний c-буфер

String templates (| ... |) у багатьох випадках швидші за конкатенацію backtick-літералів.

Неявні конверсії між різними числовими типами (напр. f + idecfloat34) — коштують. Якщо можна — тримайся одного типу:

" Добре — один тип, без конверсії
DATA: num1 TYPE decfloat34 VALUE '1.2345',
num2 TYPE decfloat34 VALUE '12345',
result TYPE decfloat34.
result = num1 + num2.
" Гірше — f у decfloat34, i у decfloat34
DATA: num1 TYPE f VALUE '1.2345',
num2 TYPE i VALUE 12345,
result TYPE decfloat34.
result = num1 + num2.

Для великих таблиць/структур — by reference (IMPORTING itab TYPE ...) замість value. Value робить копію на кожен виклик.

Для незалежних задач — CL_ABAP_PARALLEL:

DATA ref_tab TYPE cl_abap_parallel=>t_in_inst_tab.
DO 5 TIMES.
DATA(inst) = NEW lcl_parallel( ).
APPEND inst TO ref_tab.
ENDDO.
DATA(parallel) = NEW cl_abap_parallel( ).
parallel->run_inst(
EXPORTING p_in_tab = ref_tab
IMPORTING p_out_tab = DATA(result_info) ).
  • Жодного SELECT у LOOP.
  • WHERE на БД, не у ABAP.
  • Конкретні колонки, не SELECT *.
  • READ TABLE / LOOP WHERE використовують ключ.
  • Table category відповідає use case.
  • У циклах з модифікацією — ASSIGNING FIELD-SYMBOL(<fs>).
  • MODIFY ... TRANSPORTING для deep-структур.
  • FREE замість CLEAR для великих одноразових таблиць.

Адаптовано з 32_Performance_Notes.md (Apache 2.0). Повний перелік нюансів — в оригіналі.