mardi 24 mars 2015

Boost - And the winner is...

<boost/date_time/posix_time/ptime.hpp>! Ce gros fichier mange quand même 0.9 secondes de pré-compilation. Pas très loin derrière, il y a <boost/foreach.hpp>, avantageusement remplacé par le range-based for de C++11, et <boost/regex.hpp>, qui sera plus difficile à remplacer car non encore implémenté dans g++ 4.8 (je crois que c'est dans 4.9, mais je n'ai pas encore testé).

Tout cela est quand même rudement non-scientifique. Je prévois donc, lorsque j'aurai un moment, d'écrire un petit script qui me pré-compilera chaque en-tête boost une par une, pour trouver les plus coûteux.

mardi 17 mars 2015

Temps de (pré) compilation et boost::lexical_cast

Je garde un œil vigilant sur mes temps de compilation. En particulier, pour optimiser la compilation distribuée par distcc (je n'arrive pas à faire fonctionner le mode pump avec ma solution), il faut diminuer autant que faire ce peut le temps de pré-compilation, c'est à dire la phase où le compilateur va remplacer les directives #include par leur contenu, et évaluer les #if, #ifdef et autres. En effet, distcc agrège les différents en-têtes localement (car tous les fichiers sont disponibles), et distribue le (gros) fichier ainsi généré pour la phase de compilation proprement dite. L'édition de liens se fait également localement.

Or, je me suis vite rendu compte que la phase de pré-compilation prenait un temps considérable. Jusqu'à 30 secondes par fichier! Le client local passait donc sa vie à pré-compiler, alors que les CPUs distants se tournaient les pouces. Au final, la compilation entièrement locale, c'est à dire sans distcc, prenait à peu près autant de temps, car le compilateur peut optimiser la phase de pré-compilation et la phase de compilation lorsqu'elles sont faites ensemble.

Je n'ai pas encore résolu le problème plus général, mais en fouillant pour comprendre ce qui prenait autant de temps, j'ai découvert qu'un fichier d'en-tête était particulièrement coûteux: <boost/lexical_cast.hpp>.

Testez ce programme tout bête:

// lex.cpp
#include <boost/lexical_cast.hpp>

Compilez le:

time g++ -E lex.cpp -o lex.E

Sur ma machine, c'est 0.7 secondes passées juste à pré-compiler le fichier! Par comparaison, si je remplace l'inclusion par l'inclusion de l'en-tête standard pour les chaines de caractères, <string>, la phase de pré-compilation prend juste 0.025s. Alors, 0.7 secondes, ce n'est pas énorme, mais multiplié par des dizaines de milliers de fichiers, c'est non-négligeable. À noter que le temps a triplé entre boost 1.42 et boost 1.55. Cela se voit également à la taille du fichier ainsi généré: 20KLOC pour boost 1.42 contre 60KLOC pour boost 1.55.

Pour tenter de récupérer ce temps, il faut donc éliminer le plus possible les inclusions de boost::lexical_cast dans les en-têtes. Pour ce faire, il convient de remplacer les utilisations triviales par leurs équivalents moins flexibles mais souvent plus performants, et nettement plus rapides à compiler: std::to_string pour transformer les nombres en chaines, et std::stoi, std::stol, std::stod et autres pour transformer les chaines en nombres.

Pour les utilisations non-triviales, il faudra soit écrire un peu de code template pour remplacer le boost::lexical_cast par un équivalent plus simple, soit le pousser, si possible, dans le .cpp, où il n'embêtera personne d'autre.

Une fois débarassé de mes lexical_cast, je partirai à la recherche des autres mammouths, et je vous tiens au jus.

mercredi 11 mars 2015

Clang - Prémices

Je me suis mis à compiler une grosse solution C++ sous clang, le compilateur C/C++ du projet llvm, avec pour but principal de trouver des erreurs potentielles dans le code que gcc aurait laissé passer. Eh beh, c'est sport!

La partie qui m'a posé souci a été de pointer clang vers la bonne libstdc++, car mon setup était un peu bizarre. Le compilo de base sur la machine était trop vieux, et j'avais donc un g++ récent dans un répertoire non standard, que clang ne trouvait pas.

J'ai d'abord essayé d'utiliser la libc++ développée au sein du projet llvm, et me suis retrouvé avec des crash cyclopéens, potentiellement à cause de fonctionnalités manquantes dans la bibliothèque. Finalement, en jouant avec l'option "--with-gcc-toolchain", j'ai réussi à le faire pointer vers ma libstdc++ récente, et tout s'est beaucoup mieux passé.

La bonne nouvelle est qu'il n'y a presque rien à changer en termes d'options de compilation, c'est pareil que gcc. L'autre bonne nouvelle est qu'en effet, les warnings levés par clang sont tout à fait intéressants, et trouvent de vrais bugs, dont je vous parlerait dans un prochain post!