La plupart des réponses suggèrent des solutions alternatives, comme des compilateurs différents ou des bibliothèques externes, ce qui entraînerait très probablement beaucoup de travail de réécriture ou d'intégration. Je vais essayer de m'en tenir à ce que la question demande, et me concentrer sur ce qui peut être fait avec GCC seul, en activant des drapeaux de compilation ou en faisant des changements minimaux au code, comme demandé par le PO. Il ne s'agit pas d'une réponse "vous devez faire ceci", mais plutôt d'une collection d'astuces GCC qui ont bien fonctionné pour moi et que vous pouvez essayer si elles sont pertinentes dans votre contexte spécifique.
Avertissements concernant la question initiale
Avant d'entrer dans les détails, un petit avertissement concernant la question, typiquement pour les gens qui viendront, liront la question et diront "le PO optimise au-delà de O3, je devrais utiliser les mêmes drapeaux que lui !".
-
-march=native
permet l'utilisation d instructions spécifiques à une architecture CPU donnée et qui ne sont pas nécessairement disponibles sur une architecture différente. Le programme peut ne pas fonctionner du tout s'il est exécuté sur un système doté d'une unité centrale différente, ou être significativement plus lent (car cela permet également à l'utilisateur d'avoir accès à d'autres fonctions). mtune=native
), il faut donc en tenir compte si vous décidez de l'utiliser. Plus d'informations aquí .
-
-Ofast
comme vous l'avez dit, permet à certains non conforme aux normes Il faut donc l'utiliser avec prudence. Plus d'informations aquí .
Autres drapeaux GCC à essayer
Les détails pour les différents drapeaux sont listés aquí .
-
-Ofast
permet à -ffast-math
qui, à son tour, permet -fno-math-errno
, -funsafe-math-optimizations
, -ffinite-math-only
, -fno-rounding-math
, -fno-signaling-nans
y -fcx-limited-range
. Vous pouvez aller encore plus loin sur optimisations des calculs en virgule flottante en ajoutant sélectivement certains drapeaux supplémentaires comme -fno-signed-zeros
, -fno-trapping-math
et autres. Ceux-ci ne sont pas inclus dans -Ofast
et peuvent donner quelques gains de performance supplémentaires sur les calculs, mais vous devez vérifier s'ils vous profitent réellement et ne cassent aucun calcul.
- Le GCC comporte également une grande quantité de autres drapeaux d'optimisation qui ne sont pas activés par les options "-O". Elles sont listées comme des "options expérimentales qui peuvent produire du code cassé", donc encore une fois, elles doivent être utilisées avec prudence, et leurs effets vérifiés à la fois par des tests de correction et d'évaluation. Néanmoins, j'utilise souvent
-frename-registers
Cette option n'a jamais produit de résultats indésirables pour moi et tend à donner une augmentation sensible des performances (c'est-à-dire qu'elle peut être mesurée lors d'un benchmarking). C'est le type d'indicateur qui est très dépendant de votre processeur cependant. -funroll-loops
donne aussi parfois de bons résultats (et implique aussi -frename-registers
), mais cela dépend de votre code réel.
PGO
Le CCG a Optimisations guidées par le profil caractéristiques. Il n'y a pas beaucoup de documentation précise de GCC à son sujet, mais néanmoins, le faire fonctionner est assez simple.
- compilez d'abord votre programme avec
-fprofile-generate
.
- laissez le programme s'exécuter (le temps d'exécution sera sensiblement plus lent car le code génère également des informations de profil dans des fichiers .gcda).
- recompiler le programme avec
-fprofile-use
. Si votre application est multithreadée, ajoutez également l'option -fprofile-correction
drapeau.
PGO avec GCC peut donner des résultats étonnants et améliorer les performances de manière significative (j'ai constaté une augmentation de 15 à 20 % de la vitesse sur l'un des projets sur lesquels je travaillais récemment). Évidemment, le problème ici est d'avoir des des données suffisamment représentatives de l'exécution de votre application, qui n'est pas toujours disponible ou facile à obtenir.
Le mode parallèle de GCC
GCC dispose d'un Mode parallèle qui a été publié à peu près au moment où le compilateur GCC 4.2 est sorti.
En gros, il vous fournit implémentations parallèles de nombreux algorithmes de la bibliothèque standard C++. . Pour les activer globalement, il suffit d'ajouter l'option -fopenmp
et le -D_GLIBCXX_PARALLEL
au compilateur. Vous pouvez également activer sélectivement chaque algorithme lorsque cela est nécessaire, mais cela nécessitera quelques modifications mineures du code.
Toutes les informations sur ce mode parallèle se trouvent dans le site suivant aquí .
Si vous utilisez fréquemment ces algorithmes sur de grandes structures de données et que vous disposez de nombreux contextes de threads matériels, ces implémentations parallèles peuvent vous permettre d'améliorer considérablement les performances. Je n'ai utilisé que l'implémentation parallèle de sort
mais pour donner une idée approximative, j'ai réussi à réduire le temps de tri de 14 à 4 secondes dans une de mes applications (environnement de test : vecteur de 100 millions d'objets avec fonction de comparaison personnalisée et machine à 8 cœurs).
Trucs et astuces supplémentaires
Contrairement aux sections des points précédents, cette partie ne nécessitent quelques petits changements dans le code . Ils sont aussi spécifiques à GCC (certains fonctionnent aussi avec Clang), donc les macros de compilation doivent être utilisées pour garder le code portable sur d'autres compilateurs. Cette section contient des techniques plus avancées, et ne devrait pas être utilisée si vous n'avez pas une certaine compréhension au niveau de l'assemblage de ce qui se passe. Notez également que les processeurs et les compilateurs sont assez intelligents de nos jours, il peut donc être difficile d'obtenir un avantage notable des fonctions décrites ici.
- GCC builtins, qui sont listés aquí . Des constructions telles que
__builtin_expect
peut aider le compilateur à faire de meilleures optimisations en lui fournissant prédiction de branche informations. D'autres constructions telles que __builtin_prefetch
apporte les données dans un cache avant qu'elles ne soient accédées et peut aider à réduire manques dans le cache .
- les attributs de la fonction, qui sont énumérés aquí . En particulier, vous devriez examiner le
hot
y cold
le premier indique au compilateur que la fonction est une fonction hotspot du programme et optimisera la fonction de manière plus agressive et la placera dans une sous-section spéciale de la section texte, pour une meilleure localité ; le dernier optimisera la fonction pour la taille et la placera dans une autre sous-section spéciale de la section texte.
J'espère que cette réponse sera utile à certains développeurs, et je serai heureux de prendre en compte toute modification ou suggestion.