Le compilateur GCC 5.1 : harder, better, faster, stronger

72
15
mai
2015
GNU

La sortie de la nouvelle version majeure du compilateur GCC du projet GNU a été annoncée le 22 avril dernier. Écrit à l’origine par Richard Stallman, le logiciel GCC (GNU Compiler Collection) est le compilateur de référence du monde du logiciel libre. Il accepte des codes source écrits en C, C++, Objective-C, Fortran, Java, Go et Ada et fonctionne sur une multitude d’architectures.

logo GCC

Dans la suite de la dépêche, vous pourrez découvrir les nouveautés et les optimisations mises en œuvre dans cette version 5.1 de GCC.

Sommaire

Nouvelle numérotation

On peut noter que le schéma de numérotation des versions de GCC a changé puisqu’on passe directement de la version 4.9 à la 5.1. Dorénavant toutes les versions majeures seront de la forme x.1 et les versions mineures (corrections de bug) incrémenteront le dernier chiffre.

Ainsi les prochaines versions mineures seront les 5.2 puis 5.3 tandis que la prochaine version majeure, d’ici un an environ, sera la 6.1.

Intégration dans les distributions

Pour se mettre en conformité avec C++11, cette nouvelle version de GCC contient une mise à jour de l’ABI (Application Binary Interface) pour les objets std::string et std::list de la bibliothèque standard C++ qui impose une re-compilation des logiciels codés en C++ ainsi que de toutes leurs dépendances en C++.

Deux voies se dessinent pour l’intégration de cette version dans les distributions.

L'approche pragmatique

Certaines distributions ont prévu d'assurer un passage en douceur (voir le plan pour Fedora 22). Le compilateur fournira les dernières avancées du langage tout en générant du code utilisant la vieille ABI. Les logiciels utilisant les bibliothèques pre-C++11 ne devraient donc pas poser de problème.

Notons que les deux versions de l’ABI sont prises en compte par libstdc++ grâce au versionage des symboles, ce qui veut dire qu’il est possible de développer en utilisant la nouvelle version. Il suffit de définir une macro spéciale _GLIBCXX_USE_CXX11_ABI et le programme utilisera la convention C++11. Utiliser cette macro implique que les bibliothèques dépendantes utilisent aussi la nouvelle convention.

Afin de simplifier la migration vers la nouvelle ABI, les bibliothèques C++ les plus utilisées comme boost devraient être compilées avec les deux versions, comme la libstdc++. L’effort de maintenance des paquetages serait alors plus important et il n’est pas dit que tous les mainteneurs fassent cet effort.

Comme dans le cas de Fedora 23, il est souvent prévu de migrer vers la nouvelle version de l’ABI à terme. La transition se réaliserait alors sur plusieurs versions de la distribution.

La migration totale

D'autres distributions ont décidé de ne gérer qu’une seule ABI (voir le plan pour Arch Linux et Gentoo). La migration risque d’être plus douloureuse pour tout logiciel non fourni par la distribution. On pensera aux logiciels privateurs, mais aussi aux binaires que l’on cherche parfois à ne pas recompiler.

Toutefois cette approche a l’avantage de très largement simplifier la maintenance de libstdc++. Les symboles disponibles ne sont alors que ceux définis dans le standard, sans gestion de version.

Afin de réduire le risque, la gestion des symboles ABI C++98 pourrait être conservée dans la libstdc++, mais quid des bibliothèques ? Les empaqueteurs feront-ils l’effort de prendre en compte l’ancienne ABI ?

Prise en charge de DragonFly BSD

GCC est inclus depuis longtemps dans le système de base de DragonFly. Cependant, John Marino, le développeur DragonFly qui s’occupe notamment de la maintenance des deux compilateurs inclus dans base, devait maintenir un ensemble de patches pour le bon fonctionnement de GCC. Il a fini par les soumettre en amont et ces derniers ont été acceptés. DragonFly est donc depuis pris en charge par GCC de manière officielle.

Depuis le 10 février dernier, le futur GCC 5.1 est inclus dans la version en développement de DragonFly. Il remplace GCC 4.4 qui était jusqu'alors inclus aux côtés de GCC 4.7.

Le 22 avril, soit le jour de la sortie de la version stable du compilateur, GCC 5.1 remplace GCC 4.7 en tant que compilateur par défaut, ce dernier devenant le compilateur alternatif.

Nouvelles optimisations

Une passe de fusion des types C++ a été implémentée, qui permet une meilleure dévirtualisation des méthodes virtuelles utilisées par les mécanismes d’héritage.

Il est maintenant possible de spécifier l’optimisation (Ofast, O2…) au niveau des fonctions et non plus au niveau du programme global.

Les performances (utilisation mémoire et temps de calcul) de cette phase d’optimisation ont été améliorées, dans la continuité des progrès effectués depuis la première version de GCC (4.6) à proposer l’optimisation à l’édition des liens.

Selon l'excellent post d'Honza Hubička qui cite des chiffres d'Andi Kleen lors d'un build du noyau Linux, on passe d'un ralentissement de 108% de la phase de build avec GCC 4.8 à seulement 2-15% avec GCC 5.1.

Toujours dans le même article d'Honza (courrez le lire… on vous dit qu'il est excellent !) on apprend que la fonction LTO améliorée permet une réduction de 17% du code de LibreOffice (36% si on active en plus le profilage automatique).

Optimisation par profilage automatique

Donner un profil d’utilisation de l’application, au niveau code, est utile pour le compilateur. Il peut s’agir de la détection des boucles les plus fréquentes, des cas les plus probables de branches difficiles à déterminer, etc. Par exemple, s’il sait qu’un if / then / else tombe dans le else dans 80 % des cas, le compilateur pourra placer le code du else directement à la suite du if (inversant ainsi le then et le else) pour réduire les sauts de code et la pression sur le cache d’instructions.

Une infrastructure d’instrumentation du code basée sur gprof a toujours existé dans GCC (fprofile-generate, fprofile-use). Google en a proposé une nouvelle, basée sur l’outil de profilage perf proposé par Linux. Selon les notes de version, le benchmark SPEC2006 sur architecture x86-64 voit son score amélioré de 4.7% avec le nouveau auto-FDO de Google alors que l'amélioration était de 7.3% avec la fonction classique FDO (feedback directed optimization).

Si les optimisations sont de moins bonne qualité que l’infrastructure classique de GCC, le temps d’exécution du programme instrumenté (et donc le temps de récupération du profil) est très largement amélioré.

D’autres améliorations concernent la robustesse du profilage aux modifications du code source après coup. Cette activité étant chronophage, on ne veut pas avoir à la répéter trop souvent.

Allocation des registres

De manière similaire à ce qui se passe sur carte graphique, il peut être profitable de recalculer une valeur plutôt que d’aller la rechercher en mémoire. Une nouvelle passe (nommée "control-flow sensitive global register rematerialization") réalise ce travail au niveau de l’allocation des registres.
Cette nouvelle passe d'optimisation permet un gain de 1% (ARM) et 0.5% (x86-64) sur le benchmark SPEC2000.

Vectorisation

GCC 5.1 apporte une nouvelle optimisation dans le cas des séquences consécutives de loads/stores. Selon la description qu'en donne le développeur Intel Evgeny Stupachenko, ces séquences sont maintenant vectorisées automatiquement par GCC 5.1 ce qui entraine un gros gain de performance (multiplication par 6,5 par rapport à GCC 4.9 sur architecture Silvermont et par 3 sur architecture Haswell).

Assainissement du code

De nouvelles instrumentations (développées initialement pour LLVM) permettent de détecter des bugs à l’exécution. La version 4.9 de GCC proposait un mécanisme de détection d’accès mémoire interdits ou de certains comportements indéfinis par la spécification du langage. Dans GCC 5.1, il s’agit de détecter des opérations particulières sur les flottants (comme diviser par zéro), des accès à des tableaux en dehors des bornes, d’autres formes d’accès mémoire illégaux…

Nouveautés concernant les langages

Prise en charge des interfaces de programmation destinées à la parallélisation OpenMP version 4.0 (sur CPU classique et Xeon Phi) et OpenACC 2.0 (destinées aux cartes graphiques).

Langages C et C++

Gestion des extensions Intel CilkPlus (parallélisation de code). Par rapport à OpenMP qui est à l'origine plutôt orienté "parallélisation de boucles", CilkPlus est plutôt orienté "parallélisation de tâches" : par un simple mot-clef ajouté, un appel de fonction est lancé dans un thread indépendant.

Ajout de fonctions intégrées pour la récupération de débordement en arithmétique entière. Cette information n’est pas accessible simplement en C/C++ alors qu’elle est toujours remontée par le processeur. Ces fonctions ont été implémentées pour la compatibilité avec Clang. Rappelons que de nombreuses fonctions intégrées permettent d’utiliser directement une instruction assembleur précise, comme les instructions SSE.

De nombreuses fonctionnalités de C++ 2014 sont maintenant prises en charge tant par le compilateur que par la bibliothèque standard.

Go

GCC prend complètement en charge Go 1.4.2. Il fournit également les deux outils go et gofmt.

Fortran

La couverture des fonctionnalités de Fortran 2003 est presque complète avec l’ajout de trois modules d’arithmétiques IEEE (IEEE_FEATURES, IEEE_EXCEPTIONS and IEEE_ARITHMETIC). Il reste encore deux modules en cours d’implémentation (types dérivés paramétrables, entrée/sortie pour les types dérivés) et six modules dont l’implémentation est partielle sur un total de cinquante-sept.

Pour Fortran 2008, GCC propose une implémentation complète mais expérimentale des coarrays avec l’option -fcoarray=lib. C’est un bond en avant depuis une première prise en compte partielle en 2010. Il s'appuie sur le projet OpenCoarrays pour l'implémentation multi-image, qui utilise en sous main les bibliothèques MPI et GASnet (Global-Address Space Networking) pour les communications inter-nœuds.

Pour Fortran 2015, GCC prend en charge IMPLICIT NONE (external, type) et étend l'instruction ERROR STOP aux procédures pures.

Les développeurs Fortran bénéficient d’autres petites améliorations comme :

  • la coloration (partielle) de la sortie du compilateur avec l’option -fdiagnostics-color ;
  • l’option -Wtabs, modifiée pour avertir par défaut des tabulations qui empoisonnent le code () ;
  • l’option -Werror=line-truncation, activée par défaut pour prévenir de la troncature automatique des lignes ;
  • l’option -Wuse-without-only qui prévient de l’importation complète, et potentiellement erronée, de tous éléments publics d’un module ;
  • une correction importante des fonctions READ / WRITE pour les programmes localisés ;
  • des extensions pour le calcul parallèle (TS18508).

Principales nouveautés concernant les architectures matérielles cibles

ARM et ARM 64 bits (AArch64)

Adaptation de la génération du code pour certains modèles, adaptations à diverses variantes, support de nouveaux processeurs.

 AVR

Cette architecture utilisée par bon nombre de microcontrôleurs est gérée de manière différente par GCC 5. La grande variabilité des fonctionnalités nécessite maintenant d'utiliser un fichier spécifique pour l'appareil ciblé.

x86 et amd64

Support des futures instructions AVX vectorielles sur 512 bits des futurs processeurs Intel (et sans doute AMD). Actuellement, les dernières générations gèrent des registres allant jusqu'à 256 bits (AVX et AVX2). Ces instructions sont notamment utilisées par le compilateur lors de la vectorisation des boucles.

Support des futures instructions de protection mémoire Intel MPX qui aideront à détecter des erreurs mémoire de type buffer overflow dont profitent typiquement les codes malveillants. Voir cette page dédiée sur le wiki GCC qui décrit en détail cette amélioration substantielle de la sécurité du code.

Nouvelles options pour le profiling de code, l'alignement, et la gestion du registre RAX: quand les instructions SSE ne sont pas utilisées (typiquement dans le noyau Linux qui les désactive automatiquement), il arrive qu'il ne soit pas nécessaire de mettre ce registre à jour. Le registre RAX sert en effet à déterminer, selon la convention d'appel, comment sauver les registres lors d'un appel de fonction.

MIPS

Prise en charge de nouveaux processeurs et nouvelles ABI.

Rapports de bugs

Une nouvelle option a été ajoutée à GCC: quand une erreur interne de compilateur apparaît (ICE, toujours liée à un bug de GCC suite à une mauvaise gestion d'un bon ou mauvais code), il est maintenant possible de générer un rapport de bug utile aux développeurs de GCC. Ceci est valable pour la compilation de programmes C ou C++.

Appel à contribution pour la traduction française de GCC

Malgré le statut de compilateur par défaut des plates-formes libres de GCC, sa localisation n’a pas reçu toute l’attention qu’elle méritait depuis la version 3.4.3 (en 2008). Certainement parce qu’entre temps le nombre de chaînes a traduire a considérablement augmenté, passant de 4 700 à plus de 11 000.

L’équipe française du Translation Project a récemment recommencé à travailler sur la localisation de GCC. Mais vu l’ampleur du travail, toute contribution sera la bienvenue. Alors si vous appréciez un environnement parlant la langue de Molière n'hésitez pas à proposer votre aide sur la liste de diffusion de Traduc.org.

Articles de Nick Clifton

Si vous voulez suivre le développement de GCC, sans nécessairement vous plonger dans le détail des commits ou des annonces sur les listes de diffusion, un bon moyen est de suivre le blog de Nick Clifton. Ce développeur GCC propose presque chaque mois une synthèse des nouveautés.

Lire rétrospectivement les articles concernant GCC 5.1 permet de mieux mesurer les avancées de cette version :

  • # Correction

    Posté par . Évalué à 4.

    Bonjour,

    Dans le paragraphe sur les architectures matérielles (x86, x64), la phrase :

    il peut ne pas être pas nécessaire de mettre ce registre à jour.

    n'est pas claire.

    Très bonne dépêche sinon, merci !

  • # Et comparé à LLVM (clang, etc) ?

    Posté par (page perso) . Évalué à 5.

    C'est un peu bizarre de ne plus avoir de 6.0, mais pourquoi pas. Néanmoins, qu'en est-il de LLVM par rapport à GCC ?

    Il me semble que le noyau compile maintenant avec clang, et que OpenMP 3.0 est géré par clang.

  • # À bas COW, vive SSO!

    Posté par (page perso) . Évalué à 8.

    J'attends ce changement d'implémentation de std::string avec impatience. L'optimisation précédente, qui était d'utiliser de la copie sur écriture, était de moins en moins efficace dans les applications lourdement parallèles tournant sur des serveurs à grand nombre de cœurs, car il faut acquérir des mutex (cher!) et faire des barrières mémoire pour flusher les caches de chaque cœur (très cher!) tout le temps.

    Avec la nouvelle optimisation SSO (pour "short string optimisation", optimisation des petites chaînes), l'idée est que l'on peut encoder les chaines courtes au sein même de la structure de chaîne, en utilisant l'emplacement mémoire du pointeur pour y mettre ses caractères. Tant que la chaîne est plus petite que la longueur du pointeur, on y met directement les données, dès qu'elle est plus grande, cette zone mémoire redevient un pointeur vers un emplacement sur le tas. Copier des petites chaînes devient alors très bon marché. Ça tombe bien, bon nombre de programmes passent un temps formidable à copier des petites chaînes.

    J'ai commencé à recenser les bibliothèques tierces utilisées par notre système. Dès que nos amis de chez Red Hat nous sortent un Developer Toolkit avec g++ 5.1, je recompile tout!

    • [^] # Re: À bas COW, vive SSO!

      Posté par . Évalué à 5.

      Je voudrais préciser un peu l'implémentation du COW et son coût, il me semble que l'utilisation de memory barrier peut être évitée, mais comme il est tard je me trompe peut être :

      • share path : atomic_add sur le refcount, memory order relaxed.
      • destruct path: : actomic_fetch_add, si la valeur fetch est 1, alors libérer la mémoire.
      • modify path : atomic_load avec order relaxed. Si le refcount est 1, modifier les données. Sinon, copier les données dans une nouvelle chaine, changer le pointer, puis release path.

      Dans les 3 cas, il n'y a pas besoin d'utiliser des memory barrier, l'ordre relaxed est utilisable, parce que la chaine elle même est toujours immutable et sera réallouée puis copiée en cas de modification, sauf si le thread courant est l'unique propriétaire des données.

      Evidemment, le côut de l'order Relaxed diffère selon l'archi.

      • [^] # Re: À bas COW, vive SSO!

        Posté par . Évalué à 5. Dernière modification le 16/05/15 à 02:23.

        Evidemment… J'ai parlé trop vite et juste après avoir posté je me rends compte d'un bug. Un thread crée une chaine, la modifie, envoit la copie à un autre thread, qui modifie la copie alors que l'ancienne version n'a pas étée flushée : boum.

        En fait c'est intéressant parce que j'ai l'impression que c'est encore un truc qui s'implémente simplement en rust et pas en C++. Si on peut garrantir qu'il n'y a pas de mémoire partagée et que la copie est faite que depuis le même thread que celui qui possède la copie initiale, alors il suffit d'insérer une memory fence lors de la copie quand le refcount est 1.

    • [^] # Re: À bas COW, vive SSO!

      Posté par . Évalué à 3.

      L'optimisation précédente, qui était d'utiliser de la copie sur écriture, était de moins en moins efficace dans les applications lourdement parallèles tournant sur des serveurs à grand nombre de cœurs, car il faut acquérir des mutex (cher!) et faire des barrières mémoire pour flusher les caches de chaque cœur (très cher!) tout le temps.

      Est-ce qu'on a quand même des benchmarks de la différence entre la SSO et le COW sur des cas single-threadés (qui sont laaargement majoritaires je pense dans beaucoup de logiciels ou ça ne sert absolument à rien d'avoir de l'écriture sur des std::string depuis plusieurs threads distincts (pour un tant soit peu qu'il y ait besoin d'avoir plusieurs threads…)). Pas envie que ça ralentisse mon desktop ça!

      • [^] # Re: À bas COW, vive SSO!

        Posté par (page perso) . Évalué à 2.

        Le SSO est aussi intéressant à la construction, car il permet d'économiser une allocation par rapport au COW, si par exemple l'on construit les données via une constante, ou encore via un char * d'une API C (au hasard, une API de base de données) ou d'un objet différent (par exemple, extraction d'une std::string depuis un std::fstream).

        Parce que les allocations sont si chères par rapport à une bête copie de quelques octets, mon instinct est que le SSO sera un gain global de performances pour la plupart des applications. Si j'obtiens des benchmarks intéressants, je les posterai.

        • [^] # Re: À bas COW, vive SSO!

          Posté par . Évalué à 2.

          C'est sur que si ensuite il y a un constructeur constexpr ou quelque chose du genre qui atterit dans std::string, ça pourrait être un gain de perfs massif !

  • # Est-ce que le compilateur GO de GCC est le même que le compilateur GO standard ?

    Posté par (page perso) . Évalué à 2.

    GCC prend complètement en charge Go 1.4.2.

    Je comprends pas bien, est-ce que GCC intègre le compilateur Go mainstream ou est-ce que c'est un autre compilateur Go ?

  • # Optimisation au niveau des fonctions

    Posté par . Évalué à 2.

    Quelqu'un a plus d'infos sur la phrase:
    Il est maintenant possible de spécifier l’optimisation (Ofast, O2…) au niveau des fonctions et non plus au niveau du programme global.

    Genre syntaxe, utilisation, benchmark, …?

    • [^] # Re: Optimisation au niveau des fonctions

      Posté par (page perso) . Évalué à 6. Dernière modification le 16/05/15 à 19:06.

      C'est avec l'attribut optimize

      Vu la doc, je suppose que ça s'utilise comme ça :

      int __attribute__ ((optimize(2))) my_func_optimized_at_level_2(int arg);
      int __attribute__ ((optimize("O3"))) my_func_optimized_at_level_3(int arg);

  • # implicit none

    Posté par (page perso) . Évalué à 6.

    Vers 1980, je travaillais avec un FORTRAN fourni par HP. Quand j'ai eu la possibilité de déclarer "IMPLICIT NONE", j'ai demandé à mes techniciens d'utiliser cette instruction. Ils n'en étaient pas ravis, croyant perdre du temps. En réalité, cela nous en a fait gagner beaucoup. J'ai pris l'option de mettre commenter systématiquement toutes les déclarations de variables, ce qui pouvait sembler encore plus lourd, mais en fin de compte, il était beaucoup plus facile de comprendre le programme pour sa maintenance et les erreurs à la compilation ont évité de créer des variables fantômes. En fin de compte, nous avons gagné du temps en évitant des erreurs d'exécution souvent difficiles à retrouver.

    Le fameux exemple qui a fait perdre une sonde américaine n'est plus possible avec implicit none.

    Ce qui m'étonne, c'est de voir implicit none pris en compte seulement maintenant !

    • [^] # Re: implicit none

      Posté par . Évalué à 10.

      L'instruction "implicit none", qui fait partie du Fortran 90, est heureusement supporté depuis bien longtemps par gfortran. Ici, on parle de "implicit none (external)" qui fait partie du Fortran 2015 et qui semble étendre l'interdiction des implicites aux procedures et subroutines. Vous pouvez voir ici pour plus d'explication : Discussion sur la mailing-list gcc.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.