Green Abap pour l'univers SAP


Tout système numérique consomme de l'énergie lorsqu'il fonctionne. Si le matériel gagne sans cesse en efficacité, des logiciels inefficaces obligent néanmoins les serveurs à fournir des performances maximales inutiles. Les grandes entreprises gèrent généralement leurs processus métier à l'aide de systèmes SAP complexes. Le langage de programmation ABAP est au cœur de ces applications. Autrefois, les développeurs écrivaient souvent du code ABAP en ne se souciant que de sa fonctionnalité. Aujourd’hui, il vaut la peine de prêter également attention à la consommation d’énergie. En effet, un code de mauvaise qualité fait artificiellement grimper la consommation d’électricité.
Green ABAP et Clean Code
C'est là qu'intervient l'approche „ Green ABAP “. Cette méthode relie directement les objectifs de développement durable au développement logiciel. Le „ Clean Code “ joue ici un rôle essentiel. Ce terme désigne un code facile à lire et à maintenir. Un code propre réduit les erreurs et facilite les optimisations ultérieures dans la programmation. Un code facile à lire permet d’éviter la dette technique, d’économiser des ressources matérielles à long terme et contribue à la réalisation des objectifs climatiques.
Comment le test a été réalisé : la méthodologie
Nous avons étudié la consommation d'énergie du code ABAP dans le cadre d'une analyse empirique. Nous avons réalisé les tests dans un environnement SAP S/4 HANA standardisé, au sein d'un système de formation. Le SAP Workload Monitor (ST03N) a enregistré les valeurs de performance avec précision.
Nous avons toujours comparé d'anciens programmes inefficaces à des versions optimisées. La comparaison s'appuie sur quatre indicateurs clés : le temps CPU, le temps de réponse de la base de données, le temps de réponse total et le volume de données demandé.
Chaque test a été effectué exactement dix fois afin d'éviter toute fluctuation aléatoire des valeurs mesurées. Dans un système SAP, la consommation électrique directe ne peut pas être mesurée simplement en joules. Les temps de traitement du processeur et de la base de données déterminés sont toutefois généralement considérés comme des indicateurs fiables de la consommation d'énergie. Moins un programme nécessite de temps de calcul, moins le serveur consomme d'électricité. Neuf cas de programmation typiques ont été étudiés ; nous présentons ci-après les conclusions tirées ainsi que les exemples de code correspondants.
Cas n° 1 : éviter les requêtes de base de données dans les boucles
Nous avons identifié les requêtes répétées sur la base de données comme un problème majeur de performances. La version inefficace lit les enregistrements ligne par ligne à partir de la base de données.
Code inefficace :
REPORT zthesis_ineff_select_loop.
DATA : lt_flights TYPE TABLE OF sflight,
ls_flight TYPE sflight,
lv_carrname TYPE scarr-carrname.
" Chargement de tous les enregistrements de vol
SELECT * FROM sflight INTO TABLE lt_flights.
" Inefficace : instruction SELECT à l'intérieur de la boucle
LOOP AT lt_flights INTO ls_flight.
SELECT SINGLE carrname INTO lv_carrname
FROM scarr
WHERE carrid = ls_flight-carrid.
WRITE : / ls_flight-carrid, ls_flight-connid, lv_carrname.
ENDLOOP.
À des fins d'optimisation, toutes les données sont chargées au préalable dans la mémoire vive. Le programme utilise ensuite des tables internes rapides pour la comparaison des données.
Code optimisé :
REPORT zthesis_eff_select_loop.
DATA : lt_flights TYPE TABLE OF sflight,
lt_scarr TYPE TABLE OF scarr,
ls_flight TYPE sflight,
ls_scarr TYPE scarr.
" Charger les deux tables une fois
SELECT * FROM sflight INTO TABLE lt_flights.
SELECT * FROM scarr INTO TABLE lt_scarr.
" Efficace : recherche en mémoire
LOOP AT lt_flights INTO ls_flight.
READ TABLE lt_scarr INTO ls_scarr WITH KEY carrid = ls_flight-carrid.
IF sy-subrc = 0.
WRITE : / ls_flight-carrid, ls_flight-connid, ls_scarr-carrname.
ENDIF.
ENDLOOP.
Grâce à cette optimisation, le temps d'accès à la base de données a diminué de près de 99 %.
Cas n° 2 : suppression inefficace des doublons
Nous avons testé la commande ABAP permettant de supprimer les doublons. Sans tri préalable des données, le système ne supprime que les doublons directement adjacents.
Code inefficace :
REPORT zthesis_ineff_delete_dupl.
DATA : lt_data TYPE TABLE OF sbook,
ls_data TYPE sbook.
SELECT * FROM sbook INTO TABLE lt_data
WHERE customid = '00001234'.
" Pas de tri avant la suppression des doublons »
SUPPRIMER LES DOUBLONS ADJACENTS DE lt_data
EN COMPARANT carrid et connid.
LOOP AT lt_data INTO ls_data.
WRITE : / ls_data-carrid, ls_data-connid.
ENDLOOP.
Dans la version optimisée, nous avons ajouté une simple instruction de tri avant la suppression.
Code optimisé :
REPORT zthesis_opt_delete_dupl.
DATA : lt_data TYPE TABLE OF sbook,
ls_data TYPE sbook.
SELECT * FROM sbook INTO TABLE lt_data
WHERE customid = '00001234'.
" Efficacité : tri avant suppression des doublons
SORT lt_data BY carrid connid.
SUPPRIMER LES DOUBLONS ADJACENTS DE lt_data
EN COMPARANT carrid et connid.
BOUCLE SUR lt_data DANS ls_data.
ÉCRIRE : / ls_data-carrid, ls_data-connid.
FIN DE LA BOUCLE.
Cette petite modification a permis d'améliorer le temps de réponse du système de près de 47 %. Le système compare ainsi les entrées de manière bien plus efficace.
Cas n° 3 : réduire radicalement le volume des données à la source
Nous avons étudié le comportement des requêtes de base de données non filtrées. La version inefficace charge sans discernement l'intégralité de la table de données en mémoire.
Code inefficace :
REPORT zthesis_ineff_select_where.
SELECT * FROM sflight INTO TABLE @DATA(lt_flights).
LOOP AT lt_flights INTO DATA(ls_flight).
" Traiter tous les vols, même si cela n'est pas nécessaire
WRITE : / ls_flight-carrid, ls_flight-connid.
ENDLOOP.
Dans le cadre de l'optimisation, nous avons déplacé le processus de filtrage directement au niveau de la base de données en utilisant une condition “ WHERE ” ciblée.
Code optimisé :
REPORT zthesis_opt_select_where.
SELECT * FROM sflight
WHERE carrid = 'LH'
INTO TABLE @DATA(lt_flights_filtered).
LOOP AT lt_flights_filtered INTO DATA(ls_flight).
" traiter uniquement les vols pertinents »
WRITE: / ls_flight-carrid, ls_flight-connid.
ENDLOOP.
Le volume de données consultées a ainsi immédiatement diminué de plus de 73 %.
Cas n° 4 : comparer des tableaux de manière astucieuse
Les boucles imbriquées entraînent souvent des temps de calcul exponentiels lorsque les volumes de données sont importants. Nous avons comparé cette approche à celle utilisant des tables de clés efficaces.
Code inefficace :
REPORT zthesis_ineff_nested_loop.
DATA : lt_conn TYPE TABLE OF spfli,
lt_flights TYPE TABLE OF sflight.
SELECT * FROM spfli INTO TABLE lt_conn.
SELECT * FROM sflight INTO TABLE lt_flights.
LOOP AT lt_conn INTO DATA(ls_conn).
LOOP AT lt_flights INTO DATA(ls_flight).
IF ls_conn-carrid = ls_flight-carrid AND
ls_conn-connid = ls_flight-connid.
WRITE : / ls_conn-carrid, ls_conn-connid, ls_flight-fldate.
ENDIF.
ENDLOOP.
ENDLOOP.
Nous avons converti le deuxième tableau en “ table de hachage ” à des fins d'optimisation, afin que le programme puisse trouver les entrées correspondantes sans avoir à effectuer de longues recherches.
Code optimisé :
REPORT zthesis_opt_hashed_lookup.
TYPES : BEGIN OF ty_flight_map,
carrid TYPE sflight-carrid,
connid TYPE sflight-connid,
fldate TYPE sflight-fldate,
END OF ty_flight_map.
DATA : lt_conn TYPE TABLE OF spfli,
lt_flight_map TYPE HASHED TABLE OF ty_flight_map
WITH UNIQUE KEY carrid connid.
SELECT * FROM spfli INTO TABLE lt_conn.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights_small).
LOOP AT lt_flights_small INTO DATA(ls_flight).
INSERT ls_flight INTO TABLE lt_flight_map.
ENDLOOP.
LOOP AT lt_conn INTO DATA(ls_conn).
READ TABLE lt_flight_map INTO DATA(ls_match)
WITH KEY carrid = ls_conn-carrid
connid = ls_conn-connid.
IF sy-subrc = 0.
WRITE : / ls_conn-carrid, ls_conn-connid, ls_match-fldate.
ENDIF.
ENDLOOP.
Le temps CPU a ainsi pu être réduit de plus de 71 %.
Cas n° 5 : modularisation de la logique
Nous avons examiné en détail les effets d'un code bien structuré. Une logique exécutée directement dans le programme se déroule de manière linéaire.
Code inefficace (inline) :
REPORT zthesis_no_function.
DATA : lv_sum TYPE i VALUE 0,
lv_i TYPE i.
" Calculer la somme des 10 000 premiers nombres
DO 10000 TIMES.
lv_sum = lv_sum + sy-index.
ENDDO.
WRITE : / 'Somme des 10 000 premiers nombres :', lv_sum.
Nous avons ensuite externalisé cette logique de calcul dans un module fonction dédié. Cela permet de réduire au minimum les coûts liés à la structure du système.
Code optimisé :
REPORT zthesis_with_function.
DATA : lv_result TYPE i.
CALL FUNCTION 'ZTHESIS_CALC_SUM'
EXPORTING
p_limit = 10000
IMPORTING
p_sum = lv_result.
WRITE : / 'Somme des 10 000 premiers nombres (module de fonction) :', lv_result.
Le module fonction correspondant :
FONCTION zthesis_calc_sum.
DATA lv_index TYPE i.
p_sum = 0.
DO p_limit TIMES.
lv_index = sy-index.
p_sum = p_sum + lv_index.
ENDDO.
ENDFUNCTION.
La version modulaire offrait des performances pratiquement identiques. Son principal avantage réside dans une plus grande facilité d'entretien en vue d'adaptations futures.
Cas n° 6 : Approche procédurale vs approche orientée objet
Nous avons comparé une simple boucle de comptage à une approche récursive orientée objet afin de mesurer la consommation de ressources liée à la programmation orientée objet.
Code itératif :
REPORT zthesis_factorial_iterative.
START-OF-SELECTION.
DATA : lv_number TYPE i VALUE 10,
lv_result TYPE i VALUE 1.
DO lv_number TIMES.
lv_result = lv_result * sy-index.
ENDDO.
WRITE: / 'Facteur itératif (boucle) de', lv_number, ':', lv_result.
Code orienté objet :
REPORT zthesis_factorial_oop.
CLASS lcl_factorial DEFINITION.
SECTION PUBLIQUE.
MÉTHODES : calculate
IMPORTANT iv_number TYPE i
RETOURNANT VALUE(rv_result) TYPE i.
FIN DE CLASSE.
CLASSE lcl_factorial IMPLÉMENTATION.
MÉTHODE calculate.
IF iv_number calculate( 10 ).
WRITE: / 'Facteuriel récursif (POO) de 10 :', lv_result.
La variante orientée objet a consommé environ 22 % de temps CPU en plus et n'est pas rentable sur le plan énergétique pour des opérations aussi simples. Dans le cas d'architectures de grande envergure, l'avantage de l'évolutivité l'emporte toutefois à long terme.
Cas n° 7 : Éviter l'utilisation de “ SELECT “
Par souci de commodité, de nombreux développeurs utilisent la commande de base de données “ SELECT * ”. Le système charge alors aveuglément toutes les colonnes d'une table.
Code inefficace :
REPORT zthesis_ineff_select_star.
SELECT * FROM sflight
INTO TABLE @DATA(lt_flights).
LOOP AT lt_flights INTO DATA(ls_flight).
WRITE : / ls_flight-carrid, ls_flight-connid, ls_flight-fldate.
ENDLOOP.
Nous avons adapté le code et n'avons plus interrogé explicitement que les trois colonnes nécessaires.
Code optimisé :
REPORT zthesis_opt_select_fields.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights_reduced).
LOOP AT lt_flights_reduced INTO DATA(ls_flight).
WRITE: / ls_flight-carrid, ls_flight-connid, ls_flight-fldate.
ENDLOOP.
Cette réduction minime a permis d'économiser plus de 73 % du volume de données consultées.
Cas n° 8 : Flux de contrôle clair
Un code propre nécessite un déroulement clair et prévisible. Nous avons analysé une boucle comportant de nombreuses conditions d'arrêt dispersées.
Code inefficace :
REPORT zthesis_ineff_control_flow.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights).
DATA(lv_found) = abap_false.
LOOP AT lt_flights INTO DATA(ls_flight).
IF ls_flight-carrid IS INITIAL.
CONTINUE.
ENDIF.
IF ls_flight-connid IS INITIAL.
CONTINUE.
ENDIF.
IF ls_flight-fldate IS INITIAL.
CONTINUE.
ENDIF.
IF ls_flight-carrid = 'XX'.
CONTINUE.
ELSEIF ls_flight-carrid = 'YY'.
CONTINUE.
ENDIF.
IF ls_flight-connid = '9999'.
lv_found = abap_true.
EXIT.
ENDIF.
WRITE : / ls_flight-carrid, ls_flight-connid, ls_flight-fldate.
ENDLOOP.
IF lv_found = abap_true.
WRITE : / 'Connexion spéciale trouvée.'.
ENDIF.
Dans un souci d'optimisation, nous avons regroupé les nombreux sauts en une seule instruction de contrôle claire.
Code optimisé :
REPORT zthesis_opt_control_flow.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights).
DATA(lv_found) = abap_false.
LOOP AT lt_flights INTO DATA(ls_flight).
IF ls_flight-carrid IS INITIAL
OR ls_flight-connid IS INITIAL
OR ls_flight-fldate IS INITIAL
OR ls_flight-carrid = 'XX'
OU ls_flight-carrid = 'YY'.
CONTINUE.
ENDIF.
IF ls_flight-connid = '9999'.
lv_found = abap_true.
EXIT.
ENDIF.
WRITE : / ls_flight-carrid, ls_flight-connid, ls_flight-fldate.
ENDLOOP.
IF lv_found = abap_true.
WRITE : / 'Connexion spéciale trouvée.'.
ENDIF.
Cette modification n'a pratiquement pas eu d'incidence sur la durée d'exécution. En revanche, le code est désormais nettement plus lisible et plus facile à comprendre.
Cas n° 9 : Réduire l'occupation inutile de la mémoire
Certains programmes copient plusieurs fois de grands tableaux de données dans la mémoire vive. Ils créent également d'énormes tampons de texte.
Code inefficace :
REPORT zthesis_ineff_memory.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights).
DATA(lt_copy1) = lt_flights.
DATA(lt_copy2) = lt_copy1.
DATA lv_buffer TYPE string.
LOOP AT lt_copy2 INTO DATA(ls_flight).
CONCATENATE lv_buffer
ls_flight-carrid
ls_flight-connid
ls_flight-fldate
INTO lv_buffer
SEPARATED BY space.
ENDLOOP.
WRITE : / lines( lt_copy2 ).
Nous avons traité directement les données d'origine dans leur version optimisée, en évitant toute copie intermédiaire inutile.
Code optimisé :
REPORT zthesis_opt_memory.
SELECT carrid, connid, fldate
FROM sflight
INTO TABLE @DATA(lt_flights).
DATA lv_count TYPE i.
lv_count = 0.
LOOP AT lt_flights INTO DATA(ls_flight).
lv_count = lv_count + 1.
ENDLOOP.
WRITE: / lv_count.
Grâce à cet accès direct, le temps CPU a diminué de plus de 33 %.
Aperçu des économies réalisées
Les résultats des expériences d'optimisation sont parfois très nets. Le fait de ne plus effectuer de requêtes de base de données dans les boucles a permis de réduire le temps passé sur la base de données de près de 99 %. Le temps CPU a quant à lui diminué de près de 94 % lors de ce test spécifique.
Le filtrage via une condition „ WHERE “ a permis de réduire de plus de 73 % le volume de données récupérées sur le serveur. De même, le fait de renoncer à la commande pratique „ SELECT * “ a permis d'économiser plus de 73 % des données demandées. Des tables hachées intelligentes ont réduit la charge du processeur lors de la comparaison des tables de 71 %, ce qui est considérable.
Sur l'ensemble des neuf cas étudiés, le temps CPU a généralement été réduit de 20 à 90 %, ce qui est considérable. Les temps d'accès à la base de données ont également diminué de manière significative, de 30 à près de 99 %. Le tableau 1 présente un aperçu complet.
Conclusion
Green ABAP est encore peu connu, mais il s'agit d'une stratégie concrète visant à améliorer considérablement l'architecture logicielle. Un code optimisé réduit considérablement la consommation électrique d'un système. Il soulage le matériel, diminue les coûts énergétiques de l'entreprise et est bénéfique pour l'environnement. Les équipes de développeurs doivent se défaire de certaines vieilles habitudes. Les requêtes de base de données doivent être strictement regroupées et les volumes de données transférés doivent être maintenus à un niveau aussi bas que possible. Des outils tels que SAP Code Inspector facilitent la mise en œuvre systématique de ces mesures.
Depuis l'introduction de la technologie de base de données HANA, SAP prône d'ailleurs le « code pushdown », c'est-à-dire le transfert systématique des tâches de calcul vers le niveau de la base de données. La programmation durable allie l'excellence technique à une contribution importante à la protection du climat. Chaque bloc de code optimisé apporte ainsi une contribution tangible à une technologie de l'information plus verte.




