Quelquepart

Blog d'un développeur

Vous êtes ici : Accueil>Mots clés>performances

performances

Comment bien trier un tableau en Javascript

Rédigé par Sébastien Hermann dans Général, javascript - Aucun commentaire

La documentation sur internet concernant le sort est assez complète (ici par exemple), mais commençons malgré tout par un petit rappel.

Tri simple

Pour trier un tableau en JS, il suffit d'utiliser la méthode sort définie dans le prototype de Array.

Le fonctionnement par défaut de cette méthode est de faire un tri alphanumérique. Donc, si vous avez un tableau de chaines de caractères, un simple myArray.sort() devrait faire le job...

Vraiment ? Enfin, seulement si la casse est identique pour toutes vos chaines. Sinon, le résultat risque de ne pas vous plaire.

Que faire alors ?
Que vous ayez autre chose qu'un tableau de string, ou bien que vous souhaitiez que votre tri soit insensible à la casse, vous devrez alors passer à la méthode sort une fonction de tri.

Et la magie opère

const mySortFunction = (prev, next) => {
  if (prev > next) return 1; // Or any positive number
  if (prev < next) return -1; // Or any negative number
  return 0;
};
myArray.sort( mySortFunction );

Voici l'exemple type pour définir une fonction de tri croissant. Pour un tri décroissant, il suffit de retourner -1 au lieu de 1 et 1 au lieu de -1.

C'est simple et cette syntaxe peut vous permettre de gérer tout type de contenu dans votre array (objets par exemple).

Et pour les string ?

Pour trier des chaines en ignorant la casse, vous pouvez vous en sortir seul avec la fonction de tri, en appliquant un .toLowerCase() sur prev et next par exemple. Ce n'est pas très élégant, ni très performant, mais ca fait le job.

Alors on s'arrête là ?
Ce serait dommage, vu qu'il existe une méthode dédiée à la comparaison de chaines de caractères dans le prototype de String : localeCompare !

const mySortFunction = (prev, next) => prev.localeCompare(next);
myArray.sort( mySortFunction );

A propos du tri stable

On dit qu'un algorithme de tri est stable s'il ne modifie pas l'ordre initial des clés identiques (si vous n'êtes pas à l'aise avec cette notion, je vous encourage à aller lire ce très bon article sur le sujet).

Les premières versions de JS n'indiquaient pas clairement que la méthode sort devait retourner un tri stable, et donc, c'était un peu la foire, certains navigateurs l'ayant fait, d'autres non. Heureusement ES2019 a enfin levé le doute et imposé le tri stable.

Petit état des lieux :

  • IE6+: stable
  • Firefox 3+: stable
  • Chrome 70+: stable
  • Opera 10+: stable
  • Safari 4+: stable
  • Node 12+ : stable

En 2021, Node et tous les navigateurs à jour utilisent donc bien un algorithme de tri stable. Pour l'utiliser, il suffit de bien retourner 1, -1 ou 0 dans votre fonction de tri, comme spécifié dans la documentation.

Et avec le temps...

Je fais passer des tests techniques de recrutement dans ma nouvelle boite, et je vois très régulièrement des candidats qui utilisent ce genre de fonction de tri :

array.sort( (a, b) => a > b ? 1 : -1 );

Ou est passé le return 0 ? Pourquoi se contenter de retourner 1 ou -1 ? a ne peut-il donc jamais être égal à b ?

La plupart des devs me répondent qu'ils font toujours comme ca (mode copier/coller) et que "ca marche".

Les plus filous iront même jusqu'à prétendre que leur tri est "optimisé" puisqu'il fait 1 comparaison de moins, alors qu'en fait ils avaient juste la flemme d'écrire quelques caractères de plus.

Alors oui, ca marche. Mais le tri ne sera pas stable !
En effet, seul le fait de préciser un return 0 permet à l'algorithme de tri de savoir que vos 2 éléments sont identiques sur les critères de tri considérés.

Mais non ce n'est pas plus performant, c'est même généralement l'inverse, et pas qu'un peu !
La plupart des algorithmes de tri utilisent l'information d'égalité (le fameux return 0) pour optimiser leur traitement et ainsi réduire le nombre d'itérations nécessaire pour arriver à trier l'ensemble du tableau.

TODO exemple live pour les sceptiques

Empêcher le lancement de RSA1 sur ECC

Rédigé par Sébastien Hermann dans Général, Fonction - Aucun commentaire

RSA1... la transaction magique pour tout consultant BW... Cette transaction est aussi très fourbe. En effet, lorsque vous la lancez, elle crée à votre insu un job nommé BI_WRITE_PROT_TO_APPLLOG, à votre nom, avec une récurrence toutes les 5 minutes !

"Ce job est utile sur BW, donc ce n'est pas un problème", me direz-vous. Oui, mais que ce passe-t-il si vous lancer par erreur RSA1 sur un système ECC ?

La même chose, malheureusement. Même si ce job a une durée généralement inférieure à 1 seconde, il consomme un peu de puissance machine et 1 work process.

Bien sûr, l'auteur de la faute a juste à aller sur SM37 pour supprimer ce job. Mais dans la mesure ou SAP ne l'avertit pas qu'il a créé ce job pour lui, il faut déjà connaître ce mécanisme (ce qui est maintenant le cas de tous mes lecteurs ;) ).

Bien sûr, il est possible de bloquer RSA1 au niveau des autorisations, il suffit de ne pas donner ce TCODE. Mais il peut être compliqué de mettre cela en place, en fonction de la complexité de gestion des autorisations de votre entreprise.

Heureusement, il existe une autre solution, simple et rapide à mettre en place !

Lors du lancement de RSA1, SAP vérifie si le mandant en cours est conforme à celui défini pour l'exécution de BW. Pour bloquer RSA1 sur un système ECC, il suffit donc de définir un mandant "BW" non utilisé !

Comment faire ? Cette information est stockée dans la table RSADMINA, dans le champ BWMANDT. Les 2 vues de gestion permettant de modifier RSADMINA (RSADMINAV et RSADMINAV2) ne donne pas accès à cette zone, de même qu'aucune des transactions RSCUSTxx. Non, non, non, inutile de sortir votre mode debug sur SE16, il existe un module fonction : RSCC_RSADM_ACC.

En exécutant ce module fonction, vous pouvez mettre à jour le champ BWMANDT de la table RSADMINA pour le faire pointer vers un mandant qui n'existe pas sur ECC afin d'y empêcher l'exécution de RSA1.

Ce fonctionnement est décrit dans la note OSS 1949194.

Activation de DSO un peu longue ? Quelques conseils...

Rédigé par Sébastien Hermann dans Général - Aucun commentaire

Un DSO (ou ODS pour BW3.x) qui s'active en 5 minutes, un autre avec la même volumétrie qui met plus de 2 heures... Ca ne vous est jamais arrivé ?

Voici quelques pistes pour essayer de résoudre ce problème.

  • En premier lieu, même si ca peut sembler une évidence, s'assurer que les statistiques de l'ODS sont bien à jour (transaction DB20).
  • Si cet ODS n'est pas utilisé pour le reporting, s'assurer que le flag "Reporting Bex" est décoché (ou l'option "SID Generation" n'est pas sur "during activation" en BI7). Dans le cas contraire, BW profite de l'activation des données pour générer/vérifier les SID de toutes les masterdata utilisées, ce qui peut prendre beaucoup de temps !
  • L'activation peut être longue si les tables de batch sont trop grosses car elles sont utilisées lors de l'activation. Pour s'en assurer il suffit de compter le nombre d'entrées sur la table TBTCO via SE16. Si plus de 100 000 entrées sont trouvées, il est conseillé de nettoyer ces tables via le programme RSBTCDEL2 (tcode SM65). Les admins sont sensés être au courant de cette procédure.
  • En dernier lieu, il est aussi possible de faire quelques ajustements de paramétrage des ODS, via la transaction RSODSO_SETTINGS. Ces ajustements peuvent être globaux pour le serveur ou restreint au seul ODS concerné. Cette transaction n'est accessible que sur SAP BI7.x. En version BW3.x, une version primitive existe toutefois : RSCUSTA2, mais elle ne permet que des réglages globaux.
    A noter : Il est aussi possible de modifier le nombre de processus en parallele pour l'activation directement depuis la variante dans la process chain (bouton "parallel processing")

Si les problèmes persistent, alors une analyse plus poussée sera nécessaire. La note OSS 1392715 pourra alors s'avérer utile. Bon courage dans votre chasse aux performances !

Performances et boucles imbriquées

Rédigé par Sébastien Hermann dans Général - 3 commentaires

Je suis régulièrement sollicité pour résoudre des problèmes de performance sur des reports ou des interfaces.
En premier lieu, j'analyse les requêtes SQL, puis je vérifie la manière dont l'algorithme de traitement des données est implémenté.

Parmi les erreurs les plus classiques, on retrouve la gestion des boucles imbriquées. Prenons un exemple.
Nous voulons lister des données imputation de commande d'achat.
Une première table interne t_ekko contient les données d'en-tête (1000 commandes). Une seconde t_epko contient les données de poste (10 postes par commande = 10 000 postes). Une 3e t_ekkn contient les données imputations (2 imputations par poste = 20 000 imputations).
La manière simple (et anti performante) d'écrire le traitement serait :

LOOP AT t_ekko.
  LOOP AT t_ekpo WHERE ebeln = t_ekko-ebeln.
    LOOP AT t_ekkn WHERE ebeln = t_ekko-ebeln AND ebelp = t_ekpo-ebelp.
*     traitement imputation
    ENDLOOP.
*   traitement poste
  ENDLOOP.
* traitement commande
ENDLOOP.

Si cette syntaxe est rapide à écrire et simple à relire, elle présente l'inconvénient majeur que le LOOP AT txx WHERE fait un "full scan" de la table interne. Chaque ligne sera lue par le moteur pour voir si elle correspond aux critères WHERE.
Ainsi pour un report sur 1000 commandes, ayant chacune 10 postes avec 2 imputations, la table t_ekkn (20 000 entrées) est parcourue 10 000 fois, soit 200 millions de lignes lues !

Pour ce genre de tables, la lecture peut etre synchronisée entre les 3 tables via l'utilisation d'index

DATA : l_index_ekpo TYPE i,
       l_index_ekkn TYPE i.

SORT t_ekko BY ebeln.
SORT t_ekpo BY ebeln ebelp.
SORT t_ekkn BY ebeln ebelp.

LOOP AT t_ekko.
  LOOP AT t_ekpo FROM l_index_ekpo.
    IF t_ekpo-ebeln > t_ekko-ebeln.
      l_index_ekpo = sy-index.
      EXIT.
    ELSEIF t_ekpo-ebeln < t_ekko-ebeln.
       CONTINUE.
     ELSE.
       LOOP AT t_ekkn FROM l_index_ekkn.
         IF t_ekkn-ebeln > t_ekko-ebeln
        OR ( t_ekkn-ebeln = t_ekko-ebeln AND t_ekkn-ebelp > t_ekpo-ebelp ).
          l_index_ekpo = sy-index.
          EXIT.
        ELSEIF t_ekpo-ebeln < t_ekko-ebeln
        OR ( t_ekkn-ebeln = t_ekko-ebeln AND t_ekkn-ebelp < t_ekpo-ebelp ).
          CONTINUE.
        ELSE.
*         traitement imputation
        ENDIF.
      ENDLOOP.
*     traitement poste
    ENDIF.
  ENDLOOP.
* traitement commande
ENDLOOP.

Grâce aux index, chacune des 3 tables n'est parcourue qu'une seule fois, minimisant ainsi au maximum le travail de lecture du moteur ABAP. (soit 30 000 lignes lues pour t_ekkn [Les 2 lignes d'imputations + la première ligne d'imputation du poste suivant])
Néanmoins cette solution présente l'inconvénient d'être assez indigeste (peu lisible) et peut facilement introduire des bugs si elle est mal maitrisée. De plus, il faut que les 3 tables soient triées selon le même axe, ce qui n'est pas forcément possible dans tous les cas.

Aussi il existe une méthode intermédiaire, largement utilisée par SAP. Il s'agit de faire une recherche de la première ligne répondants aux critères, puis une lecture directe par index.

DATA : l_index_ekpo TYPE i,
       l_index_ekkn TYPE i.

SORT t_ekpo BY ebeln.
SORT t_ekkn BY ebeln ebelp.

LOOP AT t_ekko.
  READ TABLE t_ekpo WITH KEY ebeln = t_ekko-ebeln
                    BINARY SEARCH
                    TRANSPORTING NO FIELDS.
  IF sy-subrc = 0.
    l_index_ekpo = sy-tabix.
    LOOP AT t_ekpo FROM l_index_ekpo.
      IF t_ekpo-ebeln NE t_ekko-ebeln.
        EXIT.
      ENDIF.
      READ TABLE t_ekkn WITH KEY ebeln = t_ekko-ebeln
                                 ebelp = t_ekpo-ebelp
                        BINARY SEARCH
                        TRANSPORTING NO FIELDS.
      IF sy-subrc = 0.
        l_index_ekkn = sy-tabix.
        LOOP AT t_ekkn FROM l_index_ekkn.
          IF t_ekkn-ebeln NE t_ekko-ebeln OR t_ekkn-ebelp NE t_ekpo-ebelp.
            EXIT.
          ENDIF.
*         traitement imputation
        ENDLOOP.
      ENDIF.
*     traitement poste
    ENDLOOP.
  ENDIF.
* traitement commande
ENDLOOP.

Un peu moins performante que la syntaxe précédente, cette manière est néanmoins un peu plus simple à écrire (et à relire/comprendre).
Le read table binary search est très performant (recherche dichotomique). Pour t_ekkn contenant 20 000 entrées, seulement 14 lignes sont lues pour trouver la bonne entrée. Le moteur va donc lire 170 000 lignes sur t_ekkn au total (14 pour trouver l'imputation + 2 imputations + la première imputation du poste suivant).

Méthode Lignes t_ekkn parcourues Complexité
Loop at ... where 200 000 000 Simple
Loop from index 30 000 Complexe
Read table + Loop from index 170 000 Moyenne