Comment utiliser l'analyse statique pour améliorer la performance d'un code C++ ?

Améliorer la compilation de vos applications C++ grâce à Clang et Clazy

| Sérgio Martins | Projet Clang |

Traducteurs

| Guillaume SIGUI | Anomaly |

La plupart des compilateurs ne rentrent pas dans les détails de performance et d'analyse du respect des bonnes pratiques dans un code. Dans ce tutoriel, vous allez apprendre à intégrer un plugin dans votre compilateur pour améliorer l'analyse sémantique dans la phase de compilation.

Commentez

- Titre : Comment utiliser l'analyse statique pour améliorer la performance d'un code C++ ?

- Auteurs : Sérgio Martins, Projet Clang

- Traducteurs : Guillaume SIGUI, Anomaly

- Parution : 10 novembre 2016

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Sérgio Martins. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation de la problématique

- II. Introduction à une solution d'amélioration de la performance

- II-A. Présentation générale de la solution

- II-B. Intégration de Clazy

- II-B-1. Installation

- II-B-2. Utilisation de Clazy dans un projet

- II-B-3. Quelques tests proposés par Clazy Static Checker

- III. Aperçu du projet Clang

- III-A. Présentation générale

- III-B. Fonctionnalités

- III-C. Installation de Clang

- III-C-1. Obtenir le paquetage d'installation

- III-C-2. Installer sur les systèmes Unix

- III-C-3. Installer en environnement Visual Studio sous Windows

- III-D. Le Compiler Driver Clang, une alternative à GCC

- III-E. Quelques exemples d'utilisation de Clang

- IV. Conclusion

- Notes de la rédaction Developpez.com

I - Présentation de la problématique

L'on dit bien souvent : « n'améliorer la performance que lorsqu'un profileur vous demande de la faire ». Mais je ne suis pas du tout d'accord. Et voici trois bonnes raisons :

- prenez une grosse bibliothèque C++ comme Qt : pouvez-vous effectuer le profilage de tous les chemins des classes et des codes ? Cela prendra plusieurs années pour y arriver, et encore il faudra analyser les résultats ;

- c'est coûteux : généralement cela n'arrive que si la vitesse est suffisamment importante pour justifier le temps à consacrer à la programmation. Les clients ne paient pas pour un gain de temps de 10 % de plus, dans la performance d'une application bureautique. Même les pirates informatiques (hackers) et les amateurs d'open source ne s'engagent à optimiser que si cela vaut vraiment le coup ;

- la dernière, mais pas la moindre, c'est : « que faire si le code est uniformément lent ? » Les profileurs indiquent les goulots d'étranglement, mais si tout est rapide, ou totalement lent, rien ne se démarque vraiment. Et les résultats ne seront pas significatifs. La mauvaise performance conduit bien souvent à « la mort en mille morceaux », de faibles inefficiences sont répandues à travers tout le code, au point où elles passent inaperçues de plusieurs outils. En conséquence, bien que les profileurs soient bons et aient leur place, je recommande d'autres techniques complémentaires pour tacler 5-10 % des problèmes de la couche de cruft qui passent inaperçus.

II - Introduction à une solution d'amélioration de la performance

II-A - Présentation générale de la solution

Les compilateurs C++ sont le plus souvent simplistes : tant que le code respecte la syntaxe du langage, ils ne se plaindront pas du reste, même si vous ne suivez pas les bonnes pratiques telles que celles de Boost, STL ou Qt.

Ne serait-ce pas grandiose si les compilateurs opéraient dans une couche sémantique supérieure et ne comprenaient plus que du simple C++ ? Ils pourraient indiquer les avantages de l'algorithme STL à travers les appels std::vector::reserve() , avertir sur les détachements du conteneur Qt, ou réécrire automatiquement votre code selon les bonnes pratiques fournies par QStringLiteral et QLatin1String.

Heureusement le projet Clang vous permet justement de faire cela. Clang est une couche frontale C/C++ de l'infrastructure de compilation LLVM. Il expose un super et modulaire API qui vous permettra de tirer parti de la structure des AST personnalisés qui génèrent vos propres avertissements et erreurs.

Et donc, motivé par le fait qu'utiliser la commande grep et les expressions régulières ne résolvet pas toute la problématique, j'ai décidé d'envisager à écrire un plugin de Clang et d'en faire un compilateur qui fonctionne pour moi.

Après quelques jours de hacking et avec très peu de lignes de code, le clazy static checker est né :

1. $ clang++ -Xclang -load -Xclang ClangClazy.so -Xclang -add-plugin -Xclang clang-lazy -I /usr/include/qt/ -fPIC  -std=c++11 -c test.cpp

3. a.cpp:8:1: warning: non-POD static [-Wclazy-non-pod-global-static]

4. static QString s_string;

5. ^

7. a.cpp:24:13: warning: Reserve candidate [-Wclazy-reserve-candidates]

8. structs.push_back(TestStruct());

9. ^

11. a.cpp:37:21: warning: Missing reference on large type sizeof std::vector<TestStruct> is 24 bytes) [-Wclazy-function-args-by-ref]

12. void initialize(std::vector<TestStruct> structs)

13. ^

15. a.cpp:39:9: warning: Use QHash<K,T> instead of QMap<K,T> when K is a pointer [-Wclazy-qmap-with-key-pointer]

16. QMap<TestStruct*, int> shouldBeQHash;

17. ^

19. a.cpp:40:18: warning: Missing reference in foreach with sizeof(T) = 4000 bytes [-Wclazy-foreacher]

20. foreach (auto s, structs)

21. ^

23. a.cpp:63:21: warning: Don't call QVector::first() on temporary [-Wclazy-detaching-temporary]

24. TestStruct ts = tc.getVector().first();

25. ^

27. a.cpp:70:17: warning: QString(QLatin1String) being called [-Wclazy-qstring-uneeded-heap-allocations]

28. QString s = QLatin1String("literal");

30. 6 warnings generated.

Les vérifications relatives aux allocations de mémoire font actuellement la grosse différence et apparaissent de plus en plus sous les profileurs. Omettre les appels reserve() et les QStrings temporaires en raison de l'utilisation incorrecte de QLatin1String/char* créent de nombreuses petites allocations de pile, résultant en une fragmentation interne. Nous savons que la plupart des allocateurs sont très prompts et désireux de renvoyer ou retourner les ressources au noyau.

II-B - Intégration de Clazy

II-B-1 - Installation

Clazy est un analyseur statique orienté Qt. Il est basé sur le framework Clang.

Clang doit d'abord être installé, avant l'installation de Clazy. Vous pouvez télécharger Clazy sur la page Github.

Une fois téléchargé et décompressé dans un répertoire, il faut maintenant le compiler.

- Compilation sous Linux (clang 3.6 ou plus récent requis) :

1. $ cd clazy/

2. $ cmake -DCMAKE_INSTALL_PREFIX=<prefix> -DCMAKE_BUILD_TYPE=Release

3. $ make && make install

II-B-2 - Utilisation de Clazy dans un projet

Il est facile d'utiliser clazy dans votre projet.

Les instructions qui suivent portent sur l'environnement Linux. Sous Windows, il suffit de remplacer « clazy » par « clazy.bat ».

Vous devriez avoir la commande clazy disponible, dans le répertoire « <prefix>/bin ». Compilez vos programmes avec la commande clazy à la place de clang++ ou gcc.

Notez que cette commande est juste un raccourci qui appelle en réalité :
Pour utiliser clazy dans un projet CMake, utilisez :

1. cmake . -DCMAKE_CXX_COMPILER=clazy

Pour utiliser clazy dans un projet qmake, utilisez

1. qmake -spec linux-clang QMAKE_CXX="clazy"

II-B-3 - Quelques tests proposés par Clazy Static Checker

Pour donner une idée des possibilités du Clazy Static Checker, voici la liste des tests proposés par l'outil.

Niveau 0 : tests actifs par défaut, très fiables et sans faux positifs :

- mutable-container-key

- container-anti-pattern

- wrong-qglobalstatic

- writing-to-temporary

- unused-non-trivial-variable

- temporary-iterator

- qvariant-template-instantiation

- qstring-ref    (fix-missing-qstringref)

- qstring-insensitive-allocation

- qstring-arg

- qmap-with-pointer-key

- qgetenv    (fix-qgetenv)

- qfileinfo-exists

- qenums

- qdatetime-utc    (fix-qdatetime-utc)

- lambda-in-connect

Niveau 1 : tests actifs par défaut, très fiables mais qui peuvent être sujet à de rares faux positifs :

- missing-qobject-macro

- post-eventrange-loop

- qstring-left

- qdeleteall

- non-pod-global-static

- foreach

- detaching-temporary

- child-event-qobject-cast

- auto-unexpected-qstringbuilder    (fix-auto-unexpected-qstringbuilder)

- inefficient-qlist-soft

Niveau 2 : ces tests ne sont pas actifs par défaut parce qu'ils peuvent générer jusqu'à 30% de faux positifs :

- old-style-connect    (fix-old-style-connect)

- virtual-call-ctor

- rule-of-two-soft

- rule-of-three

- reserve-candidates

- qstring-allocations    (fix-qlatin1string-allocations,fix-fromLatin1_fromUtf8-allocations,fix-fromCharPtrAllocations)missing-typeinfo

- implicit-casts

- global-const-char-pointer

- function-args-by-value

- function-args-by-ref

- container-inside-loop

Niveau 3 : ces tests non actifs par défaut sont encore expérimentaux :

- assert-with-side-effects

- copyable-polymorphic

- detaching-member

- bogus-dynamic-cast

Pour choisir les tests à lancer, on peut passer par des variables d'environnement, par exemple :

1. export CLAZY_CHECKS="bogus-dynamic-cast,qmap-with-key-pointer,virtual-call-ctor"

Ceci ne sélectionnera que les trois tests mentionnés : bogus-dynamic-cast, qmap-with-key-pointer et virtual-call-ctor.

Pour activer tous les tests du niveau 0, sauf le test qenums :

1. export CLAZY_CHECKS="level0,no-qenums"

Pour activer tous les tests du niveau 0, ainsi que le test detaching-temporary (qui vient du niveau 1)

1. export CLAZY_CHECKS="level0,detaching-temporary" # Enables all from level0 and also detaching-temporary

On peut également choisir les tests lors de l'appel du compilateur :

1. clazy -Xclang -plugin-arg-clang-lazy -Xclang level0,detaching-temporary

III - Aperçu du projet Clang

III-A - Présentation générale

Clang est un projet qui vise à créer un nouveau front-end du compilateur LLVM pour les langages C, C+, Objective C et Objective C++.

La programmation de nouveaux front-end a été lancée à partir d'un besoin d'obtenir des compilateurs, de meilleurs diagnostics, de meilleures intégrations avec les EDI, et surtout d'avoir des compilateurs agiles qui seraient faciles à développer et à maintenir.

Clang propose une plateforme de qualité selon les exigences requises pour les environnements de production sur les architectures X86-32, X86-64 et ARM, pour les langages de programmation C, C+, Objective C et Objective C++. D'autres architectures sont en cours d'intégration, pour les futures versions.

III-B - Fonctionnalités

Les fonctionnalités principales du projet Clang sont :

- compilation :

- compilation rapide avec une faible utilisation de la ressource mémoire,

- compatibilité avec le GCC,

- possibilité de faire des diagnostics expressifs,

- un vrai compilateur respectant les exigences requises pour un environnement de production,

- un codage simple et facile à intégrer,

- un seul analyseur (parser) simplifié pour les langages C, C+, Objective C et Objective C++ ;

- utilitaires et applications :

- architecture basée sur des bibliothèques modulaires,

- possibilité d'intégration souple avec les EDI,

- utilisation de la licence BSD du LLVM.

Les descriptions de ces différentes fonctionnalités sont disponibles sur le site officiel du projet.

III-C - Installation de Clang

III-C-1 - Obtenir le paquetage d'installation

Clang est un composant des releases de LLVM. Vous pouvez le télécharger sur le site officiel.

Il est également disponible dans les ISO ou DVD de plusieurs distributions de GNU/Linux et BSD comme paquetage. Dans Xcode 4.2, Clang est le compilateur par défaut de Mac OS X.

III-C-2 - Installer sur les systèmes Unix

Pour vérifier et installer Clang en environnement Linux, voici les différentes étapes à suivre :

1. Obtenez et installez les outils prérequis :

- consultez la page des prérequis matériels et logiciels à respecter,

- il est nécessaire de télécharger et installer Python,

- cmake est également un prérequis important ;

2. Vérifiez la configuration pour l'environnement LLVM :

- positionnez-vous dans le répertoire où vous voulez installer llvm et exécutez la commande suivante :

1. svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

3. Préparez la plateforme pour recevoir Clang :

- exécutez la série de commandes suivantes :

1. cd llvm/tools

2. svn co http://llvm.org/svn/llvm-project/cfe/trunk clang

3. cd ../..

4. Préparez l'environnement pour les outils additionnels de Clang (optionnels) :

- pour cela, exécutez les commandes suivantes :

1. cd llvm/tools/clang/tools

2. svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra

3. cd ../../../..

5. Préparez l'environnement pour Compiler-RT (optionnel) :

1. cd llvm/projects

2. svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt

3. cd ../..

6. Préparez l'environnement pour libcxx (requis uniquement pour installer et exécuter Cles tests avec Compiler-RT sur OS X, il est optionnel sinon) : la préparation de l'environnement passe par les commandes suivantes :

1. cd llvm/projects

2. svn co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx

3. cd ../..

7. Construire l'environnement LLVM et Clang :

- exécutez les commandes suivantes :

1. mkdir build

2. cd build

3. cmake -G "Unix Makefiles" ../llvm

4. make

- cela installe à la fois LLVM et Clang pour le mode de débogage,

- pour une programmation avec Clang uniquement, vous devez juste exécuter : make clang,

- CMake permet de générer les fichiers de projet à partir des EDI tels que Xcode, Eclipse, CDT4, CodeBlocks, Qt-Creator (utilisez le générateur de CodeBlocks), KDevelop3, etc.

8. Si vous avez l'intention d'utiliser le support C++ de Clang, vous aurez besoin de préciser où se situe l'entête de la bibliothèque standard C++. En général, Clang va détecter la meilleure version des entêtes libstdc++ disponibles, puis va les utiliser. Il va considérer les installations système de libstdc++ comme des installations adjacentes de Clang lui-même. Si votre configuration ne respecte aucun de ces scénarios, vous pouvez utilisez l'option de cmake, DGCC_INSTALL_PREFIX pour préciser à Clang où gcc devra trouver le bon ibstdc++ installé ;

9. Essayez maintenant (assurez-vous d'avoir ajouté le chemin llvm/build/bin à votre variable d'environnement path) en exécutant la série de commandes suivantes :

1. clang -help

2. clang file.c -fsyntax-only (vérification de la syntaxe)

3. clang file.c -S -emit-llvm -o - (affichage du code llvm non optimisé)

4. clang file.c -S -emit-llvm -o - -O3

5. clang file.c -S -O3 -o - (affichage du code machine natif)

10. Exécuter le testsuite avec la commande suivante :

1. make check-clang

Si vous rencontrez des soucis pendant votre installation de Clang, assurez-vous que votre version de LLVM supporte la version de Clang. Les interfaces de LLVM changent tout le temps et toutes les versions ne sont pas compatibles entre elles.

III-C-3 - Installer en environnement Visual Studio sous Windows

Pour l'installation en environnement Visual Studio sous Windows, voici les étapes à suivre :

1. Obtenez et installez les outils prérequis :

- Subversion : contrôle du code avec subversion, à télécharger sur son site,

- CMake : utilisé pour générer les fichiers de projets de la solution Visual Studio, vous pouvez l'obtenir sur le site officiel de CMake,

- Visual Studio 2013 ou plus,

- Python : cela est seulement requis si vous souhaitez exécuter des tests (ce qui est nécessaire si vous programmez avec Clang), téléchargez-le sur le site officiel de Python,

- les outils GnuWin32 : ils sont aussi nécessaires pour l'exécution des tests. Notez que l'outil grep de MSYS ou de Cygwin ne fonctionne pas pour les tests parce qu'il intègre les doubles quotes dans le champ recherche des chaînes de caractères. Le grep du GNU fonctionne dans ce cas. Ces outils sont téléchargeables sur Sourceforge ;

1. svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

2. Vérifiez la configuration pour l'environnement LLVM :

- exécutez la commande suivante :

1. svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

3. Préparez la plateforme pour recevoir Clang :

- exécutez la série de commandes suivantes :

1. cd llvm/tools

2. svn co http://llvm.org/svn/llvm-project/cfe/trunk clang

Certains tests Clang sont sensibles aux fins de ligne. Assurez-vous d'avoir vérifié que les fichiers ne convertissent pas les fins de ligne LF en CR+LF. Si vous utilisez git-svn, assurez-vous que le paramètre core.autocrlf a la valeur false.

4. Exécutez Cmake pour générer les fichiers de projet de la solution Visual Studio avec la série de commandes suivantes :

1. cd ..\.. (retour là où vous avez démarré)

2. mkdir build (pour une installation sans modification du répertoire source dir)

3. cd build

- si vous utilisez Visual Studio 2013, exécutez la commande suivante :

1. cmake -G "Visual Studio 12" ..\llvm

- consultez le guide LLVM Cmake pour plus d'informations sur les autres configurations de Cmake,

- si la série de commandes ci-dessus s'est exécutée avec succès, il y aura un fichier LLVM.sln dans le répertoire build ;

5. Installez Clang :

- ouvrez le fichier LLVM.sln dans Visual Studio,

- installez le projet « Clang » pour seulement le compiler driver et le front-end, ou faites un « ALL_BUILD » pour construire tout le projet y compris les outils ;

6. Essayez maintenant (assurez-vous d'avoir ajouté le chemin llvm/build/bin à votre variable d'environnement path). Consultez ce lien pour plus de détails sur les tests en environnement Windows.

Une fois que vous aurez vérifié les environnements pour llvm et clang, pour synchroniser avec les dernières versions du code, utilisez la commande svn update dans les deux répertoires llvm et llvm\tools\clang, comme s'il s'agissait de deux répertoires complètement dissociés.

III-D - Le Compiler Driver Clang, une alternative à GCC

L'outil Clang est le compiler driver et le front-end indiqué pour remplacer la commande gcc.

Voici un exemple d'utilisation :

1. $ cat t.c (on crée un programme C t.c)

puis on l'édite :

1. #include <stdio.h>

2. int main(int argc, char **argv) { printf("hello world\n"); }

ensuite on exécute la compilation :

1. $ clang t.c

2. $ ./a.out

3. hello world

Le driver « Clang » est conçu pour fonctionner le mieux possible comme GCC pour en maximiser sa portabilité. La seule différence majeure entre les deux c'est que, par défaut, Clang fonctionne dans le mode gnu99, alors que GCC fonctionne dans le mode gnu89. Si vous remarquez des erreurs bizarres, essayez de passer la variable -std à gnu89 dans clang (-std = gnu89).

III-E - Quelques exemples d'utilisation de Clang

Soit le programme t.c :

1. $ cat ~/t.c

On l'édite :

1. typedef float V __attribute__((vector_size(16)));

2. V foo(V a, V b) { return a+b*a; }

Pré-traitement avec Clang :

1. $ clang ~/t.c -E

2. # 1 "/Users/sabre/t.c" 1

4. typedef float V __attribute__((vector_size(16)));

6. V foo(V a, V b) { return a+b*a; }

Vérification de Clang :

1. $ clang -fsyntax-only ~/t.c

Options GCC :

1. $ clang -fsyntax-only ~/t.c -pedantic

2. /Users/sabre/t.c:2:17: warning: extension used

3. typedef float V __attribute__((vector_size(16)));

4. ^

5. 1 diagnostic generated.

Affichage depuis AST :

Notez que l'argument -cc1 indique le front-end du compilateur qui va s'exécuter, et non le driver. Le front-end du compilateur a certaines fonctionnalités spécifiques à Clang qui ne sont pas prises en charge dans l'interface du driver de GCC.

Pour obtenir l'affichage depuis l'AST :

1. $ clang -cc1 ~/t.c -ast-print

2. typedef float V __attribute__(( vector_size(16) ));

3. V foo(V a, V b) {

4. return a + b * a;

5. }

Génération de code avec LLVM :

1. $ clang ~/t.c -S -emit-llvm -o -

2. define <4 x float> @foo(<4 x float> %a, <4 x float> %b) {

3. entry:

4. %mul = mul <4 x float> %b, %a

5. %add = add <4 x float> %mul, %a

6. ret <4 x float> %add

7. }

8. $ clang -fomit-frame-pointer -O3 -S -o - t.c # On x86_64

9. ...

10. _foo:

11. Leh_func_begin1:

12. mulps   %xmm0, %xmm1

13. addps   %xmm1, %xmm0

14. ret

15. Leh_func_end1:

IV - Conclusion

Le but de ce tutoriel est d'éveiller votre appétit et votre curiosité sur de nouvelles possibilités pour améliorer la performance de vos applications programmées en C++, en agissant sur les compilateurs. N'hésitez donc pas à apporter vos suggestions, et à présenter d'autres outils dans le fil.

Notes de la rédaction Developpez.com

Nous remercions KDAB pour l'autorisation à traduire son article. Nos remerciements également à Guillaume Sigui pour la traduction en français et Malick Seck pour la relecture orthographique.