samedi 11 avril 2015

Boost - Temps de compilation par en-tête

La bibliothèque boost est LA bibliothèque du C++ (après la bibliothèque standard, bien entendu!), bourrée d'utilitaires utiles pour fournir une base solide à son développement. Utilisée dans nombre de projets C++, un reproche fréquent est que la bestiole prend de l'embonpoint, et que ses en-têtes à ralonge plombent rapidement les temps de compilation d'un projet.

J'ai donc écrit un petit script bash qui me compile chaque en-tête l'une après l'autre et me renvoie le temps qu'il a fallu pour compiler. J'utilise g++ 4.9.2, en mode C++11 et avec l'option d'optimisation -O3. Me voilà avec la liste complète des en-têtes les plus couteuses parmi celles disponibles directement sous le répertoire "boost" et "boost/algorithm". Pas vraiment de surprises, mais la confirmation qu'il faut faire un peu attention à ses inclusions.

5.41boost/geometry.hpp
4.73boost/phoenix.hpp
3.86boost/wave.hpp
3.77boost/thread.hpp
3.24boost/random.hpp
2.92boost/signals2.hpp
2.68boost/algorithm/string_regex.hpp
2.65boost/date_time.hpp
2.57boost/program_options.hpp
2.24boost/bimap.hpp
2.21boost/flyweight.hpp
2.18boost/algorithm/string.hpp
2.15boost/algorithm/hex.hpp
2.05boost/exception_ptr.hpp
1.99boost/local_function.hpp
1.86boost/lexical_cast.hpp
1.85boost/asio.hpp
1.81boost/multi_array.hpp
1.71boost/locale.hpp
1.68boost/multi_index_container.hpp
1.68boost/assign.hpp
1.61boost/variant.hpp
1.59boost/regex.hpp
1.50boost/unordered_map.hpp
1.48boost/chrono.hpp
1.46boost/circular_buffer.hpp
1.45boost/unordered_set.hpp
1.45boost/scope_exit.hpp
1.45boost/function.hpp
1.45boost/filesystem.hpp
1.41boost/format.hpp
1.39boost/range.hpp
1.26boost/make_shared.hpp
1.24boost/optional.hpp
1.22boost/smart_ptr.hpp
1.16boost/shared_container_iterator.hpp
1.10boost/tokenizer.hpp
1.08boost/token_iterator.hpp
1.08boost/algorithm/gather.hpp
1.02boost/dynamic_bitset.hpp
1.01boost/weak_ptr.hpp
1.00boost/shared_ptr.hpp
1.00boost/enable_shared_from_this.hpp
0.99boost/shared_array.hpp
0.88boost/array.hpp
0.84boost/compressed_pair.hpp
0.82boost/any.hpp
0.80boost/algorithm/minmax.hpp
0.79boost/swap.hpp
0.68boost/type_traits.hpp
0.60boost/parameter.hpp
0.58boost/multi_index_container_fwd.hpp
0.58boost/concept_check.hpp
0.45boost/cast.hpp
0.43boost/foreach.hpp
0.41boost/iterator_adaptors.hpp
0.41boost/generator_iterator.hpp
0.38boost/ratio.hpp
0.35boost/scoped_ptr.hpp
0.35boost/rational.hpp
0.35boost/concept_archetype.hpp
0.35boost/algorithm/clamp.hpp
0.33boost/bind.hpp
0.32boost/progress.hpp
0.32boost/intrusive_ptr.hpp
0.31boost/circular_buffer_fwd.hpp
0.29boost/aligned_storage.hpp
0.28boost/utility.hpp
0.28boost/pointee.hpp
0.28boost/mem_fn.hpp
0.28boost/indirect_reference.hpp
0.27boost/operators.hpp
0.27boost/functional.hpp
0.26boost/strong_typedef.hpp
0.26boost/scoped_array.hpp
0.26boost/get_pointer.hpp
0.25boost/iterator.hpp
0.25boost/dynamic_bitset_fwd.hpp
0.24boost/next_prior.hpp
0.24boost/function_output_iterator.hpp
0.24boost/assert.hpp
0.22boost/cregex.hpp
0.15boost/nondet_random.hpp
0.10boost/blank.hpp
0.08boost/crc.hpp
0.08boost/call_traits.hpp
0.08boost/atomic.hpp
0.07boost/integer_traits.hpp
0.07boost/integer.hpp
0.06boost/timer.hpp
0.06boost/regex_fwd.hpp
0.06boost/limits.hpp
0.06boost/integer_fwd.hpp
0.06boost/implicit_cast.hpp
0.05boost/throw_exception.hpp
0.05boost/ref.hpp
0.05boost/last_value.hpp
0.04boost/visit_each.hpp
0.04boost/static_assert.hpp
0.04boost/preprocessor.hpp
0.04boost/noncopyable.hpp
0.04boost/math_fwd.hpp
0.04boost/cstdint.hpp
0.04boost/config.hpp
0.02boost/io_fwd.hpp
0.02boost/cstdlib.hpp
0.02boost/algorithm/minmax_element.hpp
0.01boost/version.hpp
0.01boost/type.hpp
0.01boost/pointer_cast.hpp
0.01boost/non_type.hpp
0.01boost/none_t.hpp
0.01boost/none.hpp
0.01boost/memory_order.hpp
0.01boost/is_placeholder.hpp
0.01boost/current_function.hpp
0.01boost/checked_delete.hpp
0.01boost/cerrno.hpp
0.01boost/blank_fwd.hpp
0.00boost/pointer_to_other.hpp
0.00boost/function_equal.hpp
0.00boost/foreach_fwd.hpp

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!

vendredi 30 janvier 2015

Bonne année!

Les stats ne sont pas belles, le nombre de posts de 2014 a encore diminué par rapport à l'année précédente... J'espère que je maintiens au moins en qualité ce que je n'arrive pas à fournir en quantité. J'ai manqué d'inspiration, pendant un temps, mais là, j'ai plein de choses à raconter. En particulier, il va falloir que je mentionne les frameworks web orientés composants, et en particulier Vaadin, en Java, et Wt, en C++. Faisant plutôt pas mal de Web, ces temps-ci, je trouve ces outils particulièrement adaptés à mes usages, en me permettant des pages au look moderne sans avoir à écrire une seule ligne de HTML ou de Javascript, dans un langage que je peux facilement interfacer avec le reste du monde.

samedi 29 novembre 2014

Test - Only If

L'on se réveille après une fête, dans une maison inconnue, sans se souvenir de rien. Partant à la découverte de la maison, l'on se retrouve dans une série d'endroits complètement surréalistes, avec pour guide un psychopathe nommé Vinny qui nous cause via des radios et qui n'arrête pas de vouloir nous tuer. Savant mix entre Dear Esther pour les décors et la faible interactivité, Portal pour la présence du guide fou, avec un petit air d'Alone In the Dark pour l'atmosphère mystérieuse, Only If est disponible gratuitement sur Steam, et vaut tout à fait le détour.

Le jeu n'est pas exempt de défauts: la difficulté est très mal dosée, et certaines séquences se passent sans presque réfléchir alors que d'autres sont ridiculement difficiles, principalement par manque d'indications sur ce que l'on est censé faire. Ainsi, il est possible de mourir de multiples fois (et de recommencer le niveau) sans vraiment avoir compris là où l'on s'est trompé. Cette approche très "old school" peut s'avérer rapidement lassante, gratuit ou pas. De plus, le scénario m'a plutôt laissé sur ma faim. J'ai trouvé l'explication finale franchement faiblarde (heureusement que quelqu'un sur Youtube a eu la bonté de rejouer la scène en ajoutant les sous-titres, parce que je n'avais vraiment pas compris grand chose au premier abord), et il n'y a pas vraiment de progression à travers les niveaux: l'on va de délire en délire, et à la fin tout est expliqué d'un coup.

Mais le soft tire son épingle du jeu via l'atmosphère tout à fait intéressante et les niveaux délirants, entre réalisme, rêve et cauchemar. Les décors sont de toute beauté, et il est agréable simplement de s'y promener. La logique, bien que franchement tordue, se tient généralement. Manquant de temps et de patience, je me suis parfois laissé à regarder les soluces, mais souvent les solutions étaient de fait trouvables, à condition de se creuser un peu les méninges.

Ah, et ça tourne sous Linux! Beaucoup de bugs graphiques, cependant, dus sans doute une fois de plus à ma config: une vieille ATI tournant avec les pilotes libres. Ça ne m'a pas empêché d'apprécier!

dimanche 16 novembre 2014

Test - The Talos Principle (demo version)

Je suis tombé complètement par hasard sur la démo du jeu "The Talos Principle", alors que je regardais ce que Steam avait à offrir ces temps-ci. Présenté comme un jeu de réflexion à la Portal, avec des graphismes plutôt accrocheurs et surtout, tournant sous Linux.

Le jeu commence dans une sorte d'île méditerranéenne, le long d'un sentier qui mène à un complexe de ruines. Grâce à de petits panneaux indicateurs, l'on comprend qu'il faut récupérer des formes géométriques rappelant Tétris dans un certain nombre de zones, dans lesquels il faut combiner différents éléments pour débloquer la forme. Il faudra par exemple utiliser une sorte d'icosaèdre posé sur trépied pour rediriger une source d'énergie vers des capteurs, ou encore brouiller des portails.

Assez rapidement, on comprend que ce monde n'est pas ce qu'il semble être. Le mélange de ruines anciennes et d'objets modernes, une voix divine qui régulièrement vient commenter nos actions, mais surtout l'on se rend compte que nos mains sont celles d'un robot, et que des terminaux permettent de converser avec une sorte d'intelligence artificielle.

Les énigmes proposent un niveau de difficulté très raisonnable, et se résolvent rapidement une fois compris les mécanismes du jeu. Certaines peuvent cependant donner un peu plus de fil à retordre si l'on n'a pas la bonne idée dès le début. Je parlais de Portal au début, et l'on retrouve des sensations similaires: des énigmes indépendantes où il faut interagir avec les objets présents, un univers qui révèle peu à peu ses secrets. Par contre, pas une trace d'humour dans The Talos Principle: au contraire, la narration, les ruines et les bribes d'information fournies sous forme d'enregistrements ou de fichiers consultables sur des consoles que l'on trouve ici et là entretiennent une atmosphère inquiétante.

Les graphismes sont tout à fait agréables, et complètement fluides avec ma configuration: une vieille ATI Radeon HD 4870 tournant avec le pilote libre radeon. Il y a cependant des bugs graphiques ici et là, qui sont sans doute liés au pilote.

Voilà en tous cas un début prometteur. Mieux que Portal? Je me permets d'en douter pour l'instant, l'absence d'humour et la mécanique des énigmes finalement peu innovante empêchera peut-être à The Talos Principle d'atteindre le status de jeu mythique. Mais cela ne m'empêchera pas de m'offrir la version complète, et qui sait, d'être convaincu!