G++ optimisation au-delà de-O3 / - Ofast
Le Problème
nous avons un programme de taille moyenne pour une tâche de simulation, que nous devons optimiser. Nous avons déjà fait de notre mieux pour optimiser la source à la limite de nos compétences en programmation, y compris le profilage avec Gprof et Valgrind.
une fois terminé, nous voulons exécuter le programme sur plusieurs systèmes probablement pour quelques mois. Par conséquent, nous sommes vraiment intéressés à pousser l'optimisation à la limite.
tous les systèmes exécuteront Debian / Linux sur du matériel relativement nouveau (Intel i5 ou i7).
La Question
quelles sont les options d'optimisation possibles en utilisant une version récente de G++, qui vont au -delà de-O3/ - Ofast?
nous sommes également intéressés par l'optimisation mineure coûteuse, qui sera payante à long terme.
Ce que nous utilisons maintenant
aujourd'hui on utilise g++ options d'optimisation:
-Ofast
: niveau d'optimisation "standard" le plus élevé. Inclus-ffast-math
n'ont pas causé de problèmes dans nos calculs, donc nous avons décidé d'aller pour elle, malgré de la non-conformité aux normes.-march=native
: permettant l'utilisation de toutes les instructions spécifiques au CPU.-flto
pour permettre l'optimisation du temps de liaison, à travers différentes unités de compilation.
8 réponses
la plupart des réponses suggèrent des solutions alternatives, comme des compilateurs différents ou des bibliothèques externes, qui apporteraient 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 les options de compilateur ou en faisant des changements minimes au code, comme demandé par L'OP. Ce n'est pas une réponse "vous devez faire ceci", mais plutôt une collection de modifications de GCC qui ont bien fonctionné pour moi et que vous pouvez essayez si elles sont pertinentes dans votre contexte particulier.
Avertissements relatifs à la question d'origine
avant d'entrer dans les détails, un petit avertissement concernant la question, typiquement pour les gens qui vont venir, lire la question et dire "L'OP optimise au-delà de O3, je devrais utiliser les mêmes drapeaux que lui!".
-march=native
permet l'utilisation de instructions spécifiques à un CPU donné architecture, et qui ne sont pas forcément disponibles sur une architecture différente. Le programme peut ne pas fonctionner du tout s'il est exécuté sur un système avec un CPU différent, ou être significativement plus lent (car cela permet aussimtune=native
), donc être conscient de cela, si vous décidez de l'utiliser. Plus d'information ici.-Ofast
, comme vous l'avez indiqué, permet à certains non conforme à la norme optimisations, il doit donc être utilisé avec prudence. Plus d'informations ici.
autres drapeaux GCC à essayer
les détails des différents drapeaux sont listés ici.
-Ofast
permet-ffast-math
, qui à son tour permet-fno-math-errno
,-funsafe-math-optimizations
,-ffinite-math-only
,-fno-rounding-math
,-fno-signaling-nans
et-fcx-limited-range
. Vous pouvez aller encore plus loin optimisation du calcul en virgule flottante en ajoutant sélectivement drapeaux-fno-signed-zeros
,-fno-trapping-math
et pour les autres. Ces ne sont pas inclus dans-Ofast
et peut donner quelques augmentations de performance supplémentaires sur les calculs, mais vous devez vérifier si elles vous profitent réellement et ne cassent pas des calculs.- GCC dispose également d'une grande quantité de autres indicateurs d'optimisation qui ne sont pas activées par les options "-O". Ils sont listés comme "options expérimentales qui peuvent produire du code brisé", donc encore une fois, ils doivent être utilisés avec prudence, et leurs effets vérifiés à la fois par des tests pour l'exactitude et de l'analyse comparative. Néanmoins, j'utilise souvent
-frename-registers
, cette option n'a jamais produit de résultats indésirables pour moi et tend à donner une notable augmentation des performances (ie. peut être mesurée lors de l'analyse comparative). C'est le type de drapeau qui est très dépendante de votre processeur.-funroll-loops
donne aussi parfois de bons résultats (et implique aussi-frename-registers
), mais il dépend de votre code.
PGO
GCC a Optimisations Guidées Par Les Profils fonctionnalités. Il n'y a pas beaucoup de documentation précise de GCC à ce sujet, mais néanmoins le faire fonctionner est assez simple.
- compilez d'abord votre programme avec
-fprofile-generate
. - lancez le programme (le temps d'exécution sera beaucoup plus lent car le code génère également des informations de profil .gcda fichiers).
- recompiler le programme avec
-fprofile-use
. Si votre application est multi-thread également ajouter le-fprofile-correction
drapeau.
PGO avec GCC peut donner des résultats étonnants et vraiment augmenter considérablement la performance (j'ai vu une augmentation de 15-20% de la vitesse sur l'un des projets sur lesquels je travaillais récemment). De toute évidence, le problème ici est d'en avoir données suffisamment représentatives de l'exécution de votre application, qui n'est pas toujours disponible ou facile à obtenir.
mode parallèle de GCC
CCG features a Mode Parallèle, qui a d'abord été publié au moment où le compilateur GCC 4.2.
en gros, il vous fournit implémentations parallèles de plusieurs algorithmes dans la bibliothèque Standard C++. Pour activer globalement, vous avez juste à ajouter le -fopenmp
et -D_GLIBCXX_PARALLEL
drapeaux sur le compilateur. Vous pouvez également activer sélectivement chaque algorithme si nécessaire, mais cela nécessitera quelques modifications mineures du code.
Tous les des informations sur ce mode parallèle peuvent être trouvées ici.
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 donner un énorme coup de pouce aux performances. Je n'ai utilisé que la mise en œuvre parallèle de sort
jusqu'à présent, mais pour donner une idée, j'ai réussi à réduire le temps pour le tri de 14 à 4 secondes dans une de mes applications (environnement de test: vecteur de 100 des millions d'objets avec la fonction de comparaison personnalisée et 8 machines de noyaux).
astuces
Contrairement aux sections précédentes, cette partie fait besoin de quelques petits changements dans le code. Ils sont aussi spécifiques à GCC (certains d'entre eux fonctionnent sur Clang), donc les macros de temps de compilation doivent être utilisées pour garder le code portable sur d'autres compilateurs. Cette section contient quelques techniques plus avancées, et ne devrait pas être utilisé si vous n'avez pas compréhension de ce qui se passe au niveau de l'Assemblée. Notez également que les processeurs et compilateurs sont assez intelligents de nos jours, il peut donc être difficile d'obtenir tout avantage notable des fonctions décrites ici.
- GCC objets internes, qui sont énumérés ici. Des constructions comme
__builtin_expect
peut aider le compilateur à faire de meilleures optimisations en lui fournissant branche de prédiction informations. Autres constructions telles que__builtin_prefetch
apporte des données dans un cache avant il est accessible et peut aider à réduire cache. - attributs de fonction, qui sont listés ici. En particulier, vous devriez regarder dans le
hot
etcold
attributs; le premier indiquera au compilateur que la fonction est un hotspot du programme et d'optimiser la fonction de manière plus agressive et de la placer dans une sous-section spéciale de la section texte, pour une meilleure localisation; la dernière optimisera la fonction pour la taille et le placer dans un autre paragraphe de la section de texte.
j'espère que cette réponse sera utile pour certains développeurs, et je serai heureux d'examiner si des modifications ou des suggestions.
relativement nouveau matériel (Intel i5 ou i7)
pourquoi ne pas investir dans une copie du compilateur Intel et les bibliothèques haute performance? Il peut surpasser GCC sur les optimisations par une marge significative, typiquement de 10% à 30% ou même plus, et encore plus pour les programmes lourds de nombre-crunching. Et Intel fournit également un certain nombre d'extensions et de bibliothèques pour des applications de calcul de nombres (parallèles) de haute performance, si c'est quelque chose que vous pouvez vous permettre à intégrer dans votre code. Cela pourrait rapporter gros si cela finit par vous sauver des mois de course.
nous avons déjà fait de notre mieux pour optimiser la source à la limite de nos compétences en programmation
d'après mon expérience, le type de micro - et nano - optimisations que vous faites habituellement avec l'aide d'un profileur ont tendance à avoir un faible retour sur investissement dans le temps par rapport aux macro-optimisations (rationalisation de la structure du code) et, plus important encore, et souvent négligées, les optimisations d'accès à la mémoire (par exemple, localisation de référence, traversée dans l'ordre, minimisation indirecte, utilisation de cache-manques, etc.). Ce dernier consiste habituellement à concevoir les structures de la mémoire pour mieux refléter la façon dont la mémoire est utilisée (traversée). Parfois, il peut être aussi simple que de changer un type de conteneur et d'obtenir un énorme gain de performance. Souvent, avec profilers, vous vous perdez dans les détails des optimisations instruction par instruction, et de la mémoire les problèmes de mise en page ne se manifestent pas et sont généralement manqués lorsque vous oubliez de regarder la vue d'ensemble. C'est une bien meilleure façon d'investir votre temps, et les gains peuvent être énormes (par exemple, de nombreux algorithmes O(logN) finissent par fonctionner presque aussi lentement que O(N) juste en raison de la mauvaise disposition de la mémoire (par exemple, l'utilisation d'une liste ou d'un arbre lié est un coupable typique d'énormes problèmes de performance par rapport à une stratégie de stockage contigu).
Si vous pouvez vous le permettre, essayez VTune. Il fournit beaucoup plus d'informations que le simple échantillonnage (fourni par gprof, autant que je sache). Vous pourriez donner l' Analyste De Code un essai. Ce dernier est un logiciel libre décent, mais il pourrait ne pas fonctionner correctement (ou du tout) avec les processeurs Intel.
étant équipé d'un tel outil, il vous permet de vérifier diverses mesures telles que l'utilisation du cache (et essentiellement la mise en page de la mémoire), qui - si elle est utilisée à sa pleine extension - fournit un énorme coup de pouce pour l'efficacité.
lorsque vous êtes sûr que vos algorithmes et vos structures sont optimaux, alors vous devriez certainement utiliser les noyaux multiples sur i5 et i7. En d'autres termes, jouer avec différents algorithmes/modèles de programmation parallèle et voir si vous pouvez obtenir une accélération.
lorsque vous avez des données vraiment parallèles (structures de type tableau sur lesquelles vous effectuez des opérations similaires/identiques), vous devez donner OpenCL et instructions SIMD(plus facile à configurer) un essai.
hein, puis dernière chose que vous pouvez essayer: ACOVEA projet: Analyse des optimisations des compilateurs via un algorithme évolutif -- comme il ressort clairement de la description, il essaie un algorithme génétique pour choisir les meilleures options de compilateur pour votre projet (faire maaany times compilation et vérifier le timing, donner un feedback à l'algorithme :) -- mais les résultats pourraient être impressionnants! :)
quelques notes sur la réponse actuellement choisie (je n'ai pas encore assez de points de réputation pour poster ceci comme commentaire):
La réponse dit:
-fassociative-math
,-freciprocal-math
,-fno-signed-zeros
et-fno-trapping-math
. Ces ne sont pas inclus dans-Ofast
et peuvent donner une certaine supplémentaire de la performance augmente sur les calculs
C'était peut-être vrai lorsque la réponse a été affichée, mais le documentation GCC dit que tout cela est activé par -funsafe-math-optimizations
, qui est activée par -ffast-math
, qui est activée par -Ofast
. Cela peut être vérifié avec la commande gcc -c -Q -Ofast --help=optimizer
, qui montre les optimisations sont activées par -Ofast
, et confirme que toutes ces options sont activées.
La réponse dit aussi:
autres options d'optimisation qui ne sont pas activées par les options "-O"...
-frename-registers
encore une fois, la commande ci-dessus montre que, au moins avec mon GCC 5.4.0,-frename-registers
est activé par défaut -Ofast
.
Il est difficile de répondre sans plus de détails:
- quel type de calculs?
- quelles bibliothèques utilisez-vous?
- quel degré de paralysie?
Pouvez-vous écrire la partie de votre code qui prend le plus de temps? (Généralement une boucle)
si vous êtes lié au CPU, la réponse sera différente de si vous êtes lié au IO.
Encore une fois, veuillez fournir plus de détails.
je recommande d'examiner le type d'opérations qui constituent le levage lourd, et de chercher une bibliothèque optimisée. Il y a beaucoup de bibliothèques rapides, optimisées pour l'assemblage, vectorisées SIMD pour les problèmes courants (surtout les mathématiques). Réinventer la roue est souvent tentant, mais il est généralement pas la peine de l'effort si une solutionexistante peut couvrir vos besoins.Comme vous n'avez pas précisé quel type de simulation il s'agit, Je ne peux fournir que quelques exemple.
avec gcc intel tour de / implémenter-FNO-gcse (fonctionne bien sur gfortran) et-fno-guess-branch-prbability (par défaut dans gfortran)