Optimisation des performances ABAP : Developper des programmes plus rapides

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

L’optimisation des performances est cruciale pour des programmes ABAP conviviaux et economes en ressources. Cet article presente les techniques les plus importantes pour ameliorer les performances.

Outils d’analyse des performances

OutilTransactionDescription
SQL TraceST05Analyser les acces a la base de donnees
ABAP TraceSATAnalyse du temps d’execution
Code InspectorSCIAnalyse statique du code
ABAP ProfilerSATProfilage detaille
Explain PlanST05Plan d’execution SQL

Optimisation SQL

Lire uniquement les colonnes necessaires

" Mauvais - lire toutes les colonnes
SELECT * FROM mara INTO TABLE @DATA(lt_mara)
WHERE mtart = 'FERT'.
" Bon - uniquement les colonnes necessaires
SELECT matnr, maktx, mtart, matkl
FROM mara
INTO TABLE @DATA(lt_mara_opt)
WHERE mtart = 'FERT'.

Utiliser les index avec SELECT

" Mauvais - pas d'index utilisable
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr.
" Bon - utiliser l'index primaire
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE vbeln = @lv_vbeln
AND posnr = @lv_posnr.
" Bon - utiliser un index secondaire (si disponible)
SELECT * FROM vbap INTO TABLE @DATA(lt_vbap)
WHERE matnr = @lv_matnr
%_HINTS ORACLE 'INDEX(VBAP VBAP~Z01)'.

Utiliser correctement FOR ALL ENTRIES

" La verification de table vide est OBLIGATOIRE !
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 au lieu de 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).

Utiliser les fonctions d’agregation

" Mauvais - lire toutes les donnees et faire la somme
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 ).
" Bon - la base de donnees fait la somme
SELECT SUM( kwmeng ) AS total
FROM vbap
WHERE vbeln = @lv_vbeln
INTO @DATA(lv_sum_db).

Activer la mise en buffer

" Activer la mise en buffer de table dans SE11 :
" - Mise en buffer complete (petites tables)
" - Mise en buffer generique (partie des champs cles)
" - Mise en buffer par enregistrement
" Contourner le buffer si necessaire
SELECT SINGLE * FROM t001 BYPASSING BUFFER
INTO @DATA(ls_t001)
WHERE bukrs = @lv_bukrs.

Operations en masse

" Mauvais - INSERTs individuels
LOOP AT lt_new_data INTO DATA(ls_data).
INSERT ztable FROM ls_data.
ENDLOOP.
" Bon - INSERT tableau
INSERT ztable FROM TABLE lt_new_data.
" Bon - UPDATE tableau
UPDATE ztable FROM TABLE lt_update_data.
" Bon - DELETE tableau
DELETE ztable FROM TABLE lt_delete_data.

Tables internes

Choisir le bon type de table

" STANDARD TABLE - acces sequentiel, APPEND
DATA: lt_standard TYPE STANDARD TABLE OF sflight.
" SORTED TABLE - recherche binaire, acces trie
DATA: lt_sorted TYPE SORTED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.
" HASHED TABLE - acces direct par cle
DATA: lt_hashed TYPE HASHED TABLE OF sflight
WITH UNIQUE KEY carrid connid fldate.

Optimiser READ TABLE

" Mauvais - recherche lineaire
READ TABLE lt_standard INTO DATA(ls_line)
WITH KEY carrid = 'LH'.
" Bon - recherche binaire (la table doit etre triee !)
SORT lt_standard BY carrid.
READ TABLE lt_standard INTO ls_line
WITH KEY carrid = 'LH' BINARY SEARCH.
" Bon - TABLE SORTED ou HASHED
READ TABLE lt_sorted INTO ls_line
WITH TABLE KEY carrid = 'LH' connid = '0400' fldate = '20250115'.

Utiliser les cles secondaires

" Definir une cle secondaire
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.
" Acces via la cle secondaire
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.

Optimisation LOOP

" Mauvais - WHERE sans index
LOOP AT lt_large_table INTO DATA(ls_line)
WHERE status = 'A'.
ENDLOOP.
" Bon - WHERE sur table triee
LOOP AT lt_sorted INTO ls_line
WHERE carrid = 'LH'. " Utilise le tri
ENDLOOP.
" Bon - ASSIGNING au lieu de INTO (pas de copie)
LOOP AT lt_table ASSIGNING FIELD-SYMBOL(<ls_line>).
<ls_line>-status = 'P'. " Modification directe
ENDLOOP.
" Bon - REFERENCE INTO pour les operations de lecture
LOOP AT lt_table REFERENCE INTO DATA(lr_line).
DATA(lv_value) = lr_line->field.
ENDLOOP.

Jointures de tables en memoire

" Mauvais - LOOPs imbriques
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.
" Bon - SORTED TABLE avec 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.

Parallelisation

Appels RFC paralleles

DATA: lv_task TYPE string,
lv_counter TYPE i,
lt_results TYPE TABLE OF ty_result.
" Demarrer des taches paralleles
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 : traiter de maniere synchrone
CALL FUNCTION 'Z_PROCESS_PACKAGE"
EXPORTING
is_package = ls_package
IMPORTING
es_result = DATA(ls_result).
APPEND ls_result TO lt_results.
ENDIF.
ENDLOOP.
" Attendre toutes les taches
WAIT UNTIL lv_completed = lv_counter UP TO 300 SECONDS.
" Methode de callback
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.

Framework SPTA

" SPTA pour le traitement parallele
DATA: lt_input TYPE spta_t_input,
lt_output TYPE spta_t_output.
" Preparer l'entree
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.
" Traiter en parallele
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.

Operations sur les chaines

Concatenation efficace

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

Mettre en cache les expressions regulieres

" Mauvais - recompiler la regex a chaque appel
LOOP AT lt_strings INTO DATA(lv_string).
FIND REGEX '\d{4}-\d{2}-\d{2}' IN lv_string.
ENDLOOP.
" Bon - compiler la regex une seule fois
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.

Optimisation memoire

Liberer les grandes tables

" Liberer explicitement la memoire
CLEAR lt_large_table.
FREE lt_large_table.
" Ou traiter avec REDUCE
DATA(lv_sum) = REDUCE i( INIT s = 0
FOR <line> IN lt_large_table
NEXT s = s + <line>-amount ).
FREE lt_large_table. " Liberer immediatement

Traitement par paquets

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.
" Traiter le paquet
LOOP AT lt_package INTO DATA(ls_line).
" Traitement...
ENDLOOP.
FREE lt_package.
lv_offset = lv_offset + lv_limit.
ENDDO.

Mise en cache

Objets Shared Memory

" Shared Memory pour les donnees frequemment lues
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.
" Verifier le Shared Buffer
IMPORT config = rt_config FROM SHARED BUFFER indx(zc)
ID 'CONFIG'.
IF sy-subrc <> 0.
" Charger les donnees
SELECT * FROM zconfig INTO TABLE rt_config.
" Sauvegarder dans le Shared Buffer
EXPORT config = rt_config TO SHARED BUFFER indx(zc)
ID 'CONFIG'.
ENDIF.
ENDMETHOD.
ENDCLASS.

ABAP Shared Objects

" Shared Objects pour les donnees complexes
" 1. Definir l'area (SHMA)
" 2. Creer la classe racine
TRY.
" Obtenir le handle de l'area
DATA(lo_handle) = zcl_my_area=>attach_for_read( ).
DATA(lo_root) = lo_handle->get_root( ).
" Lire les donnees
DATA(lt_data) = lo_root->get_cached_data( ).
lo_handle->detach( ).
CATCH cx_shm_error.
" Fallback
ENDTRY.

Analyse des performances

Analyser le SQL Trace

" Activer ST05
" Executer le programme
" Analyser le trace
" Problemes typiques :
" - Identical Selects (meme requete plusieurs fois)
" - SELECT * (trop de colonnes)
" - Index manquant
" - Trop d'enregistrements individuels

Runtime Analysis (SAT)

" Creer un trace SAT
SET RUN TIME ANALYZER ON.
" ... executer le code ...
SET RUN TIME ANALYZER OFF.
" Ou via la transaction SAT
" - Vue agregee
" - Hit List
" - Call Hierarchy

Verifications Code Inspector

" SCI - variantes de verification importantes :
" - SELECT dans LOOP
" - APPEND dans LOOP au lieu de tableau
" - Condition WHERE manquante
" - INTO CORRESPONDING FIELDS
" - Operations de table inefficaces

Mesure et benchmarking

DATA: lv_start TYPE i,
lv_end TYPE i,
lv_diff TYPE i.
GET RUN TIME FIELD lv_start.
" Code a mesurer
LOOP AT lt_table INTO DATA(ls_line).
" ...
ENDLOOP.
GET RUN TIME FIELD lv_end.
lv_diff = lv_end - lv_start.
WRITE: / 'Temps d''execution:', lv_diff, 'microsecondes'.
" Ou avec des 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 ).

Bonnes pratiques

  1. Mesurer avant d’optimiser : Toujours profiler d’abord, puis optimiser
  2. Reduire la charge base de donnees : Moins de SELECTs, WHERE efficaces
  3. Bon type de table : HASHED pour l’acces par cle, SORTED pour les plages
  4. Liberer la memoire : FREE pour les grandes tables apres utilisation
  5. Parallelisation : Pour les taches independantes
  6. Mise en cache : Pour les donnees frequemment lues, rarement modifiees

Transactions importantes

TransactionDescription
ST05Trace SQL/Buffer
SATTrace ABAP
SCICode Inspector
ST12Trace combine
SM50Work processes
ST02Statistiques buffer

Sujets connexes