Greboca  

Suport technique et veille technologique

Aujourd’hui, les grandes entreprises et administrations publiques hésitent entre continuer à utiliser des logiciels propriétaires ou basculer vers les Logiciels Libres. Pourtant, la plupart des logiciels libres sont capables de bien traiter les données issues des logiciels propriétaire, et parfois avec une meilleur compatibilité.

C’est alors la barrière de la prise en main qui fait peur, et pourtant...

Les logiciels libres

L’aspect « Logiciel Libre » permet une évolution rapide et une plus grande participation des utilisateurs. Les aides et tutoriels foisonnent sur Internet ou sont directement inclus dans le logiciel lui-même.

Enfin, les concepteurs sont plus proches des utilisateurs, ce qui rend les logiciels libres plus agréable à utiliser et conviviaux.

Grâce à la disponibilité des logiciels libres, vous trouverez facilement des services de support techniques et la licence n’est plus un frein à l’utilisation de ces logiciels par votre personnel.

Notre support technique concerne essentiellement les logiciels libres, que ce soit sous forme de services ponctuels ou de tutoriels.

LinuxFr.org : les journaux  -  Prise de poids et perte de perf

 -  Juillet 2023 - 

Sommaire

Bonjour 'nal,

Il m'est arrivé un truc de ouf, une énigme de dev comme je n'en avais pas vu depuis longtemps : par un malheureux concours de circonstances mon application en C++ a pris 5% de temps d'exécution en plus suite à la suppression d'une seule ligne, un #include .

Accroche-toi, il s'avère que la cause de l'augmentation du temps d'exécution était uniquement liée à l'augmentation de la taille du binaire. Mais pourquoi diable sa taille a-t-elle augmentée en supprimant cette inclusion ? Attrape un mug de ton breuvage favori, je vais te détailler l'enquête. C'est parti.

Effet de bord par inclusions

Devant un comportement inattendu, sans cause apparente, il faut commencer par restreindre les possibilités en isolant le changement qui a causé la régression. Pour ça il n'y a pas de mystère, j'ai pris la liste des 42 fichiers que j'avais modifiés et je les ai restaurés en dichotomie jusqu'à faire disparaître le problème. Le fichier coupable était, disons, bar.cpp.

Dans ce fichier j'avais juste enlevé l'inclusion de utils.hpp, tu sais, le genre de fichier qui inclus plein de trucs pour maximiser le couplage. En la remettant le problème disparaissait. Comme il n'y avait pas de rapport direct entre utils.hpp et bar.cpp j'ai supprimé un niveau en remplaçant cette inclusion par une autre, indirectement faite dans utils.hpp. Nommons cet autre entête base.hpp. Les perfs sont toujours bonnes avec ce changement. Bien, ça fait sens.

Ne voyant rien dans base.hpp qui pouvait expliquer la différence de perfs, et comme il inclut plein de fichiers, j'ai décidé de prendre un raccourci en regardant le code traité par le compilateur après l'étape du préprocesseur :

  1. Récupérer la commande qui compile bar.cpp.o en lançant make VERBOSE=1 ou ninja -v. Ça ressemble à g++ -I… -f… -D… -o …/bar.cpp.o -c …/bar.cpp
  2. Retirer le fichier de sortie et ajouter l'option -E pour afficher la sortie du préprocesseur : g++ -I… -f… -D… -E -c …/bar.cpp.

J'ai gardé la sortie du préprocesseur avec et sans l'inclusion de base.hpp et j'ai fait un diff entre les deux. Il y avait pas mal de différences. En cherchant une piste dans le source, je regarde dans bar.cpp et je vois qu'il utilise std::abs. Je me concentre là dessus et je vois que la version avec base.hpp en a plusieurs définitions. L'une est celle de cstdlib (i.e. la fonction abs de la lib C) :

inline long
abs(long __i) { return __builtin_labs(__i); }

L'autre est celle de cmath (i.e. la fonction abs de la STL) :

template<typename _Tp>
inline constexpr
typename __gnu_cxx::__enable_if<__is_integer<_Tp>::__value,
                                    double>::__type
abs(_Tp __x)
{ return __builtin_fabs(__x); }

Et oui, la version de std::abs fournie avec GCC 4.8 utilise fabs même pour les paramètres entiers, on a donc une double conversion : int -> float puis float -> int. Ce n'est que si cstdlib est inclus que la version long est disponible et préférée par rapport au template.

Et là il faut que je précise que ce n'est pas la faute de GCC mais bien d'une ambiguïté du standard C++ qui a été résolue à l'époque de GCC 7 (cf. LWG 2192 et LWG 2294). Pas de chance pour moi, je suis coincé avec GCC 4.8 :'(

Confirmer la cause

Pour confirmer que le problème vient bien de là je remplace l'inclusion de base.hpp par cstdlib. Les perfs sont toujours correctes \o/

J'ai isolé le problème à un entête, il ne reste plus qu'à confirmer que c'est bien lié à l'appel à std::abs. Je commence par faire un diff des instructions du programme avec et sans l'inclusion de cstdlib. Désassembler un programme sous une forme « diffable » n'est pas immédiat, notamment à cause des adresses des sauts et appels de fonctions qui vont être différentes au moindre ajout d'instruction. Pour contourner cela j'ai bricolé avec la commande suivante

objdump --demangle \
        --disassemble \
        --no-show-raw-insn \
        -M intel \
        my_program \
    | sed 's/ \+#.\+$//' \
    | sed 's/0x[a-f0-9]\+/HEX/g' \
    | sed 's/\(\(call\|j..\) \+\)[0-9a-f]\+/\1HEX/' \
    | sed 's/^\([ ]\+\)[0-9a-f]\+:/\1  HEX:/'

Quelques explications sur la commande. L'outil objdump avec ces paramètres va afficher les instructions en assembleur qui correspondent au programme. Ensuite j'envoie la sortie dans sed pour remplacer ce qui gêne le diff par des informations génériques (ici en plusieurs commandes pour la lisibilité du journal) :

  • les commentaires de fin de ligne sont supprimés ;
  • les valeurs hexadécimales (e.g. pour les accès mémoire) remplacées par HEX ;
  • les adresses pour les appels de fonctions et les sauts remplacées par HEX ;
  • les adresses des instructions remplacées par HEX.

Transformées ainsi les sorties peuvent être envoyées dans diff ce qui montre clairement que la seule différence impactante entre les versions avec et sans cstdlib est l'ajout de conversions int <-> float au niveau des appels à std::abs :

cvtsi2sd  xmm0, edi    ; entier vers float
andps     xmm0, XMMWORD PTR .L_2il0floatpacket.0[rip]
cvttsd2si eax, xmm0    ; float vers entier

Effet de bord de l'effet de bord

Tout cela est bien joli mais ça n'explique toujours pas la perte de performance. Vois-tu, ces fonctions qui font des conversions int <-> float ne sont pas exécutées par le benchmark. Pour celui-ci on utilise une version alternative implémentée avec des intrinsèques AVX2. Ce n'est donc pas le changement de l'implémentation de abs qui fait la différence.

Par contre, en regardant le diff d'assembleur, je vois bien qu'il y a un paquet d'instructions supplémentaires quand cstdlib est absent ; j'en compte à peu près 100 000, pour environ 500 ko sur la taille du binaire. Maintenant, si tout cela concerne du code non-exécuté, notre meilleure piste est que ça gêne la récupération des instructions pour l'exécution du reste du programme. J'essaye de confirmer cela en utilisant l'outil perf pour mesurer les cache-misses pour les instructions (évènement L1-icache-load-misses), et bingo : 11 à 20% de cache-misses supplémentaires quand cstdlib est absent. A-ha ! Enfin !

Chose étonnante, j'observe cette perte quand mon binaire passe de 12,1 Mo à 12,6 Mo (tailles obtenues en activant LTO), mais pas de perte en passant de 13,2 à 13,7 Mo (tailles obtenues en désactivant LTO). Cette énigme restera de côté pour l'instant.

Bilan

J'ai voulu nettoyer mes inclusions et, parce que j'utilise un vieux compilateur, ça a changé le code généré. Le changement a causé une augmentation de la taille de mon binaire, taille qui a dépassé un seuil causant une forte augmentation des cache-misses et donc une perte de performances.

Pfiou, ce n'était pas grand chose mais ça m'a pris des semaines. Tout ça a cause de la suppression d'une inclusion d'entête. Comme quoi il vaut mieux être trop inclusif… Hein, quoi ? Ah, on me dit que je mélange tout. Tant pis, c'est fini.

P.S. : Sur le sujet de la taille des binaires il y a une série de billets sympa par Sandor Dargo, par exemple ici : https://www.sandordargo.com/blog/2023/07/19/binary-sizes-and-compiler-flags

Commentaires : voir le flux Atom ouvrir dans le navigateur

par Julien Jorge

LinuxFr.org : les journaux

LinuxFr.org : Journaux

Téléphone sous Linux ?

 -  25 avril - 

Aujourd'hui, avoir un téléphone avec un Android libéré, c'est possible, on pense en particulier à Murena.Avoir un téléphone sous GNU/Linux, c'est (...)


Quand votre voiture vous espionne… et vous le fait payer

 -  23 avril - 

Ceci se passe aux États-Unis, pour l’instant, aucune preuve qu’une telle fuite existe en Europe. Mais… si votre assurance augmente brutalement, (...)


firefox, nouvelle fenêtre dans une session isolée

 -  15 avril - 

Les fenêtres de navigation privées de firefox partagent leurs cookies de session or je souhaitais avoir des fenêtres de navigation isolées, (qui ne (...)


Pretendo tente de déprogrammer l'obsolescence des consoles Nintendo

 -  9 avril - 

Ah Nal,Gros N vient de faire un gros doigt aux utilisateurs de ses consoles 3DS et Wii U en annonçant la fermeture des services en ligne pour (...)


[Trolldi] Vulgarisation sur l'IA pour décideur pressé

 -  5 avril - 

Cher 'Nal,Je fais un article-marque-page sur un post tout frais de Ploum où il est question d'un fantasme vieux comme le Talmud avec le Golem. (...)