Оптимизация ABAP-а

Автор Uukrul, Травень 16, 2008, 11:39:58 ДП

Попередня тема - Наступна тема

0 Користувачі і 1 Гість дивляться цю тему.

Uukrul

Данное первое сообщение темы будет содержать обобщенный документ по оптимизации ABAP.

25.10.2010 - Первая версия документа 1.0

21.03.2011 - Изменения к оптимизации ABAP версия 1.1
Цитата
Краткий список изменений:

  • Расширено описание пункта 8 – Определение внутренних таблиц, добавлен раздел 8.1 описывающий внутреннюю организацию и принципы выделения памяти системой для внутренних таблиц, так же описан параметр INITIAL SIZE используемый при объявлении внутренних таблиц в программах и общие рекомендации по его использованию.
  • Расширено описание пункта 8 – Определение внутренних таблиц, добавлен раздел 8.2 описывающий организацию индексов для внутренних таблиц и принципов их построения.
  • Расширено и изменено описание пункта 9, работа операторов REFRESH и FREE, в связи с получением более подробной информации по их функционированию.
  • Небольшие несущественные правки по тексту из-за внесенных выше изменений в раздел 8

21.09.2011 - Изменения к оптимизации ABAP версия 1.2
Цитата
Краткий список изменений:

  • Расширено описание пункта 10 – Тестирование программ, дополнено описание использования транзакций расширенной проверки программы SLIN и анализатора код транзакции SCI / SCII.

PS: Предыдущие версии документа, пока удаляться не будут...
PPS: И кстати качать предыдущие версии документа не обязательно, хотя если вы хотите отследить как менялось мое и всех кто помогал мне писать документ, представление об оптимизации ABAP-а, ну тогда да, качайте  ;D

Uukrul

#1
В общем как не крутись а писать на ABAP приходится и очень часто это какие-то отчеты/обработки, которые гудят долго и жрут как памяти так и процессора много, в общем-то зубры и так знают как писать правильно, а вот молодЕжь такие перлы выдает, что иногда... хотя опять же если вспомнить прошлый век и свой первый отчет, то как говориться сам такой был. А поэтому предлагают тут поделиться примерами того как надо писать, чтобы или работало быстрее или памяти жрало меньше или.. ну в общем от ситуации.

PS: При этом не надо думать, что если что-то вам кажется общеизвестным, то это таки действительно общеизвестно  ;)

Работу запросов можно проверить через транзакцию ST05 включив трассировку запросов, однако в продуктиве можете отгрести за это, плюс не забываем что два чтения подряд одних и тех же данных, дадут разное время, так как во втором чтении данные скорее всего будут уже частично кешированы и соответственно прочитаются быстрее без изменения запроса.

Выборка данных из таблиц.


DATA: lt_mara LIKE mara OCCURS 1 WITH HEADER LINE.

SELECT * INTO CORRESPONDIG FIELDS OF TABLE lt_mara
FROM mara

работает медленнее, чем
SELECT * INTO TABLE lt_mara
FROM mara


В общем случае время выполнения первого запроса раза в 4 больше чем второй вариант.

Паганель

-- а что лутше select sum(xxx) from yyy или  ....

ЦитатаВ общем как не крутись а писать на ABAP приходится и очень часто это какие-то отчеты/обработк
-- ну вижу (у нас) то знание абап - только вред - так тебя начинают грузить программерской работой, никагого росту, за деревьями леса не видиш ....
а рядок девушки  - абпапа нифига - так у них и задачи поинтересней,  и загрузка полутше ...

p.s. может тему начать ? Типа консультант без знания абап, хорошо или плохо ....
или "Насколько большой недостаток - знать абап"

Uukrul

Цитата: "Паганель" від Травень 16, 2008, 10:21:16 ПП
-- а что лучше select sum(xxx) from yyy или  ....
Для больших таблиц... ну скажем так, как отрабатывает операция SUM(XXX), при такой конструкции всегда подразумевается что есть группа записей с одинаковым значением одного поля и числовым полем, т.е.

ID COST
------------
01 2
02 2
01 3
01 1
02 1

Без индекса к чему это ведет? А к тому что при операции суммирования, для каждого из значений ID в общем случае система пробегает по всей таблице, да кеширование и оптимизация при обработки SQL-запросов + индексы ускорят процесс, но в общем случае без индекса сумму для каждого ID можно получить выполнив для данного примера двойной проход по таблице, сначала для ID = 01, затем для ID = 02, а поэтому иногда может быть быстрее загрузить все во внутреннюю таблицу (если по памяти есть куда) и уже в памяти пройтись и просуммировать данные.

Опять же, все таки желательно смотреть на данные и уже из этого решать, что будет быстрее, т.е. запрос с суммированием или чтение SELECT * INTO TABLE xxx и уже затем LOOP AT lt_xxx. ENDLOOP. С суммированием в цикле.

Из личного опыта на большой таблице, >30 000 000 записей, но записи длинной порядка 100 байт, мне надо было посчитать не сумму, а количество записей с определенными ID. Так вот, операция SELECT COUNT(*) просто зависло и не вернулось из задумчивости, а вот вариант с выбором во внутреннюю таблицу и ручным суммированием, оказался очень даже быстрым и приемлимым. Индексы были построены и первый SELECT по плану запроса ходил как раз именно по индексу.

Цитата: "Паганель" від Травень 16, 2008, 10:21:16 ПП
а рядок девушки  - абпапа нифига - так у них и задачи поинтересней,  и загрузка полутше ...
p.s. может тему начать ? Типа консультант без знания абап, хорошо или плохо ....
или "Насколько большой недостаток - знать абап"
Ну открывай, в другой ветке конечно  ::)

Паганель

Цитатауже в памяти пройтись и просуммировать данные.

Можеш пример в студию - простенький лиш бы понять смысл ?

Uukrul

#5
Цитата: "Паганель" від Травень 16, 2008, 11:10:29 ПП
Можеш пример в студию - простенький лиш бы понять смысл ?
Так а чего там пример то? Ну схематично, без системы типа так:

*&---------------------------------------------------------------------*
*& Report  YUUK_TEST
*&
*&---------------------------------------------------------------------*
*& В общем пример суммы свободного запаса по заводам для материалов
*&
*&---------------------------------------------------------------------*

REPORT  yuuk_test.
DATA: BEGIN OF lt_mard OCCURS 1.
        INCLUDE STRUCTURE mard.
DATA: index LIKE sy-tabix,
END OF lt_mard.
DATA: l_mard LIKE lt_mard.

*--------------------------
SELECT * INTO TABLE lt_mard
FROM mard
ORDER BY werks matnr.

* Опять же может окажется что SORT lt_mard BY werks matnr.
* может быстрее быть чем сортировка в запросе. Тут надо смотреть
* на память и объемы выборки...

LOOP AT lt_mard.
  ON CHANGE OF lt_mard-werks OR lt_mard-matnr.
    IF sy-tabix = 1.
      lt_mard-index = sy-tabix.
      l_mard = lt_mard.
    ELSE.
      MODIFY lt_mard FROM l_mard INDEX l_mard-index.
      lt_mard-index = sy-tabix.
      l_mard = lt_mard.
    ENDIF.
    CONTINUE.
  ENDON.
  l_mard-labst = l_mard-labst + lt_mard-labst.
ENDLOOP.
IF lt_mard-index = 0.
  l_mard-labst = l_mard-labst + lt_mard-labst.
ENDIF.
MODIFY lt_mard FROM l_mard INDEX l_mard-index.

DELETE lt_mard WHERE index = 0.

LOOP AT lt_mard.
  WRITE: / lt_mard-werks, lt_mard-lgort, lt_mard-matnr, lt_mard-labst.
ENDLOOP.

Паганель

Спасибо, что то такое я и думал ... только не мог вспомнить где видел подобное ...
Кажется аналогично делается отчет с суммами по группам с помощю WRITE ...

Вопрос: а зачем так
ЦитатаDELETE lt_mard WHERE index = 0.

Как я понял - для очистки строки (шапки таблицы) ...

А чего не clear ...  сейчас еще посмотрю ..ю

Uukrul

Цитата: "Паганель" від Травень 17, 2008, 12:17:07 ДП
Вопрос: а зачем так
Как я понял - для очистки строки (шапки таблицы) ...
Нет, конечно... я убираю из памяти лишние запаси которые уже вошли в суммирование... т.е. пример есть пять записей на двух заводах одного материала:

1000 0088 M1 1
1000 0001 M1 2
1000 0088 M1 5
1000 0010 M1 3
1200 0001 M1 4

Так вот после выборки с сортировкой я получу, ну пусть такой порядок:
1000 0001 M1 2
1000 0088 M1 5
1000 0088 M1 1
1000 0010 M1 3
1200 0001 M1 4

После обработки будет такая ситуация во внутренней таблице:
1000 0001 M1 11 1
1000 0088 M1 5 0
1000 0088 M1 1 0
1000 0010 M1 3 0
1200 0001 M1 4 5

Затем я оператом DELETE грохну нулевые индексы и получу
1000 0001 M1 11 1
1200 0001 M1 4 5

На склад забиваем и того имею суммы запасов материала по заводу в целом.

Uukrul

#8
Поля в примере идут:
Завод, Склад, Код материала, Количество

В результирующей выборке, последнее число, это индекс записи внутренней таблицы... из примера программы это поле index.

Uukrul

#9
Самый тормознутый способ выборки чего либо из таблиц это операция:

SELECT * FROM mard.
ENDSELECT.

Однако исключать данный способ из применения не следует, так как он дает наименьшую нагрузку на сервер + минимальное использование памяти. Так что если таблица небольшая или выборка данных будет небольшой, то вполне можно обойтись и данной конструкцией, т.е. без операции SELECT * INTO TABLE и дальнейшим LOOP AT lt_tab. ENDLOOP.  Ну и как обычно смотрим трассировку запросов. Если эта конструкция является самой тормознутой в вашем приложении, то имеет смысл ее заменить, а иначе, нефиг заниматься оптимизацией так где и без нее все бегает  ;)

№1

Цитата: Uukrul від Травень 16, 2008, 11:39:58 ДП
PS: При этом не надо думать, что если что-то вам кажется общеизвестным, то это таки действительно общеизвестно  ;)
Добавлю свои пять копеек... про конструкцию SELECT .... FOR ALL ENTRIES IN itab
Если itab пустая, то будет перебор всей таблицы, т.е. FULL SCAN. Поэтому надо до оператора поставить проверку на непустоту.
ЗЫ. Непонятно одно - почему САПы не воткнули такую проверку на уровне ядра? Жить было бы куда проще.
Мой блог

Uukrul

Ну поехали дальше (Опять же из обще известного, так сказать примеры из документации SAP, руки до которой почему-то у многих не доходят):

Требуется проверить на существование записи в таблице БД по определенному не полному ключу. Варианта тут есть два, первый делаем SELECT с нужными ключами и внутри EXIT.

SELECT * FROM <table> INTO <work_line>
    WHERE <Field> = 'xxx'.
  EXIT.
ENDSELECT.

или

SELECT * FROM <table> INTO <work_line>
  UP TO 1 ROWS
  WHERE <Field> = 'xxx'.
ENDSELECT.

Так вот второй вариант с UP TO 1 ROWS (кстати многие похоже о такой конструкции даже не подозревают), работает так раз в 6-7 быстрее, чем первый вариант.

Uukrul

Ну и дальше возвращаясь к теме агрегирующих функций, собственно с чего началась данная тема, вообще-то документация советует использовать именно функции агрегации типа MAX, MIN, SUM и т.д. вместо использования построчного подсчета значений. В справке приведены такие два примера, по поиску максимального значения:


DATA: MAX_MSGNR type t100-msgnr.
MAX_MSGNR = '000'.
SELECT * FROM T100 INTO T100_WA
  WHERE SPRSL = 'D' AND
        ARBGB = '00'.
  CHECK: T100_WA-MSGNR > MAX_MSGNR.
  MAX_MSGNR = T100_WA-MSGNR.
ENDSELECT.

и

DATA: MAX_MSGNR type t100-msgnr.
SELECT MAX( MSGNR ) FROM T100 INTO max_msgnr
  WHERE SPRSL = 'D' AND
        ARBGB = '00'.

Разница в выполнении этих двух примеров где-то раз в 100, т.е. первый пример будет работать ну на много дольше. Однако если вернуться к конструкции UP TO 1 ROWS, то добавив конструкцию ORDER BY с соответсующей сортировке по убыванию  для MAX или по возрастанию для MIN получим время выполнения такое же как и в примере с агрегирующей функцией.  Пример ниже:

DATA: MAX_MSGNR type t100-msgnr.
MAX_MSGNR = '000'.

SELECT * FROM T100 INTO T100_WA UP TO 1 ROWS
  WHERE SPRSL = 'D' AND
        ARBGB = '00'
ORDER BY MSGNR DESCENDING.
ENDSELECT.

В общем замеры времени выполнения для примера 2 и 3 показали 81 и 85 миллисекунд (для примера 1, кстати было 7360), т.е.  для функций MIN и MAX, можно использовать альтернативу UP TO 1 ROWS совместно с ORDER BY. Ну а сумма и среднее значение, это уже по обстоятельствам надо смотреть  ;)

Uukrul

По поводу данных которые мы выбираем. В общем не следует без всякой необходимости использовать * в операторе SELECT, если вас интересует значение одного или пары полей из сотни, которые входят в таблицу. Пример из справки:

SELECT * FROM DD01L INTO DD01L_WA
  WHERE DOMNAME LIKE 'CHAR%'
        AND AS4LOCAL = 'A'.
ENDSELECT.

и

SELECT DOMNAME FROM DD01L
  INTO DD01L_WA-DOMNAME
  WHERE DOMNAME LIKE 'CHAR%'
        AND AS4LOCAL = 'A'.
ENDSELECT.

В первом случае  получаем время выполнения порядка 8285 миллисекунд, а во втором случае 2596. Ну т.е. где-то раза в 4.

Однако теперь, зная что и как выбирать и возвращаясь к предыдущему сообщению, по поводу агрегирующих функций, пример 3 можно еще дожать следующим образом:

DATA: MAX_MSGNR type t100-msgnr.

SELECT MSGNR FROM T100 INTO (MAX_MSGNR) UP TO 1 ROWS
  WHERE SPRSL = 'D' AND
        ARBGB = '00'
ORDER BY MSGNR DESCENDING.
ENDSELECT.

т.е. задав выбор только поля по которому требуется получить максимальное значение и после этого выполнение станет практически такое же как и с использованием функции MAX, у меня получилось после нескольких прогонов разница в 1-2 миллисекунды. В общем так как не ассемблер, то на эти миллисекунды уже можно и забить.

Uukrul

Если имеются часто изменяемые данные в таблицах, например это очереди в таблице сообщений, то читают такие данные обычно с выключением буфера. Однако не следует этим перебарщивать, а то скорость обработки ясное дело будет печальной. Примеры из справки:

SELECT SINGLE * FROM T100 INTO T100_WA
  BYPASSING BUFFER
  WHERE     SPRSL = 'D'
        AND ARBGB = '00'
        AND MSGNR = '999'.

и

SELECT SINGLE * FROM T100  INTO T100_WA
  WHERE     SPRSL = 'D'
        AND ARBGB = '00'
        AND MSGNR = '999'.

В общем случае скорость для первого запроса ~80 миллисекунд, а во втором порядка 5 миллисекунд.

Uukrul

Возвращаясь к вариантам выбора записей из таблиц и их последующей обработке есть два варианта, выбрать все и затем пройтись по внутренней таблице или же обрабатывать данные по мере из выбора внутри конструкции SELECT ENDSELECT. В общем случае быстрее будет выбрать данные во внутреннюю таблицу и затем обработать выборку. Примеры:

SELECT * FROM T006
  INTO TABLE X006.
LOOP AT X006 INTO X006_WA.
ENDLOOP.

и

SELECT * FROM T006 INTO X006_WA.
ENDSELECT.

Первый вариант работает быстрее в среднем в полтора-два раза чем вариант два. По замерам время исполнения в среднем колебалось 60 и 110 миллисекунд.

Uukrul

Вложенные SELECT или SELECT с JOIN. Я вообще-то предпочитаю  использовать конструкцию JOIN хотя как показывают замеры, первый вариант работает быстрее. В общем случае констуркция

SELECT * FROM SPFLI INTO SPFLI_WA.
  SELECT * FROM SFLIGHT INTO SFLIGHT_WA
      WHERE CARRID = SPFLI_WA-CARRID
        AND CONNID = SPFLI_WA-CONNID.
  ENDSELECT.
ENDSELECT.

была быстрее чем конструкция

SELECT * INTO WA
    FROM SPFLI AS P JOIN SFLIGHT AS F
      ON P~CARRID = F~CARRID AND
         P~CONNID = F~CONNID.
ENDSELECT.

По времени выполнения  получается 6100 миллисекунд против 8300 во втором случае. Но пять же, требуется смотреть на таблицы из которых выбираются данные и на объем который требуется выбрать.

Uukrul

Подзапросы или FOR ALL ENTRIES IN T_SPFLI с использованием двух операторов SELECT. Исходя из примера по справке, существуют следующие варианты выборки:

SELECT * FROM SPFLI
  INTO TABLE T_SPFLI
  WHERE CITYFROM = 'FRANKFURT'
    AND CITYTO = 'NEW YORK'.
SELECT * FROM SFLIGHT AS F
    INTO SFLIGHT_WA
    FOR ALL ENTRIES IN T_SPFLI
    WHERE SEATSOCC < F~SEATSMAX
      AND CARRID = T_SPFLI-CARRID
      AND CONNID = T_SPFLI-CONNID
      AND FLDATE BETWEEN '19990101' AND '19990331'.
ENDSELECT.

и

SELECT * FROM SFLIGHT AS F INTO SFLIGHT_WA
    WHERE SEATSOCC < F~SEATSMAX
      AND EXISTS ( SELECT * FROM SPFLI
                     WHERE CARRID = F~CARRID
                       AND CONNID = F~CONNID
                       AND CITYFROM = 'FRANKFURT'
                       AND CITYTO = 'NEW YORK' )
      AND FLDATE BETWEEN '19990101' AND '19990331'.
ENDSELECT.

В общем случае второй вариант работает быстрее, 210 простив 130 миллисекунд, чем использование FOR ALL ENTRIES.  Кстати по поводу FOR ALL ENTRIES, не забываем, что если табличка T_SPFLI, будет пустой, то как уже писал №1, будет полный проход таблицы.

bdmalex

ВВерну свои 5 копеек...

Начиная с какой-то версии 4.x(точно не скажу) - есть прекрасный инструмент: Сode Inspector(Код инспектор)
...
IMHO - помогает бороться с ошибками(явными и не очень) ЛЮБЫХ абаперов...:)...
...
А вообще - инфа на курсах ADM315(немного) и BC490....

№1

Цитата: bdmalex від Червень 27, 2008, 07:07:17 ПП
прекрасный инструмент: Сode Inspector(Код инспектор)
Тада свой полтинник... от избыточных выборок по базе это чудо бессильно 8)
Мой блог

Uukrul

Цитата: bdmalex від Червень 27, 2008, 07:07:17 ПП
Начиная с какой-то версии 4.x(точно не скажу) - есть прекрасный инструмент: Сode Inspector(Код инспектор)
Это транзакция SLIN имеется в виду?

№1

Мой блог

Uukrul


№1

А в 4.6С надо отдельным транспортом это тащить - есть нота
Мой блог

Uukrul

Цитата: №1 від Липень 01, 2008, 07:43:45 ПП
А в 4.6С надо отдельным транспортом это тащить - есть нота
А номерок ноты? А то я что-то не нашел, о разных исправлениях ошибок есть... а саму программку где тянуть не находиться...