Pratiques de codage qui permettent au compilateur/optimiseur de faire un programme plus rapide
Il y a plusieurs années, les compilateurs C ne sont pas particulièrement intelligent. Comme une solution de contournement K&R inventé le registre de mots clés, de faire allusion à l'compilateur, que ce serait peut-être une bonne idée de garder cette variable dans un registre interne. Ils ont également fait le tertiaire opérateur pour aider à générer un code de meilleure qualité.
Comme le temps passait, les compilateurs mûri. Ils sont devenus très intelligent dans leur analyse des flux de leur permettre de prendre de meilleures décisions sur ce que les valeurs de tenir dans les registres que vous pourriez éventuellement faire. Le mot-clé de registre est devenu sans importance.
FORTRAN peut être plus rapide que C pour certains types d'opérations, en raison de alias questions. En théorie, prudent de codage, on peut contourner cette restriction pour activer l'optimiseur de générer plus rapidement le code.
Quelles pratiques de codage sont disponibles qui peuvent permettre au compilateur/optimiseur de générer plus rapidement le code?
- L'identification de la plate-forme et le compilateur que vous utilisez, serait appréciée.
- Pourquoi la technique semble fonctionner?
- Exemple de code est encouragée.
Ici est un une question relative à la
[Modifier] Cette question n'est pas sur le processus global de profil, et de les optimiser. Supposons que le programme a été écrit correctement, compilé avec une optimisation complète, testés et mis en production. Il peut être construit dans votre code qui interdisent l'optimiseur de faire leur travail du mieux qu'il peut. Que pouvez-vous faire pour refactoriser le code qui permettra de supprimer les interdictions, et permettre à l'optimiseur de générer encore plus rapide code?
[Modifier] Décalage lien
- Pourrait être un bon candidat pour le wiki de la communauté à mon humble avis car il n'y a pas de "unique" en réponse définitive à cette (intéressant) question...
- Je la rate à chaque fois. Je vous remercie pour ça.
- Par "meilleur", vous voulez dire simplement "plus vite" ou avez-vous d'autres critères d'excellence à l'esprit ?
- Oui. Je vous remercie. Je vais affiner la question.
- Je pensais c, mais il n'y a pas vraiment de raison de le limiter à cela. Je vais mettre à jour les tags
- Je me demande à propos de cette histoire de l'inscrire mot-clé. Ils ont écrit un OS, peut-être que cela a du sens parce que la cible de leur système?
- Il est assez difficile d'écrire un bon allocateur de registres, en particulier de façon portable, et l'allocation de registres est absolument essentielle à la performance et de la taille du code.
register
effectivement fait la performance sensible code plus portable par la lutte contre les pauvres, les compilateurs. - wiki de la communauté ne veut pas dire "pas de réponse définitive", ce n'est pas synonyme avec le caractère subjectif de la balise. Wiki de la communauté signifie que vous souhaitez abandonner votre poste à la communauté, afin que d'autres personnes peuvent le modifier. Ne vous sentez pas obligé de wiki à vos questions, si vous ne vous sentez pas comme lui.
- J'ai fait un certain nombre de ces. La question est bonne. Je m'attends à un truc que je ne sais pas qui, qui va ajouter une flèche de mon carquois. Rep est un effet secondaire. Je suis d'accord avec Chris. Il n'y a pas de meilleure réponse. J'ai Choisi celui qui me semblait le plus surprenant. Les variables locales montrent également automatiquement dans un msvc fenêtre. C'est un plus.
Vous devez vous connecter pour publier un commentaire.
Écrire des variables locales et non pas des arguments de sortie! Cela peut être d'une grande aide pour faire le tour de l'aliasing, des ralentissements. Par exemple, si votre code ressemble à
le compilateur ne sait pas que toto1 != barOut, et a donc pour recharger toto1 chaque passage dans la boucle. Il a également ne peut pas lire foo2[i] jusqu'à ce que l'écriture barOut est fini. Vous pourriez commencer à déconner avec les pointeurs restreints, mais il est tout aussi efficace (et beaucoup plus claire) pour ce faire:
Il semble stupide, mais le compilateur peut être beaucoup plus intelligent de traiter avec la variable locale, étant donné qu'il ne peut pas se chevauchent dans la mémoire de l'un quelconque des arguments. Cela peut vous aider à éviter la redoutable charge-hit-store (mentionné par François Boivin dans ce fil).
munge
n'est pas inlinable; voir stackoverflow.com/questions/7761810/...Foo
etmunge
. Considérons plutôt ceci: void somme(const float* vals-les-bains, int numVals, float& sumOut){ sumOut = 0.0 f; for (int i=0; i<numVals; i++) {sumOut += vals[i]; } } un Peu artificiel, mais qui aurait certainement profiter de faire la somme d'une variable locale.Voici une pratique de codage à l'aide du compilateur de créer rapidement de code—toute langue, de toute plate-forme, un compilateur, un problème:
Ne pas utiliser toutes les astuces judicieuses de la force, ou même de l'encourager, le compilateur de jeter les variables dans la mémoire (cache et registres) que vous pensez le mieux. Premier à écrire un programme qui est correct et facile à entretenir.
Ensuite, le profil de votre code.
Alors, et alors seulement, vous pouvez commencer à étudier les effets de dire au compilateur comment l'utilisation de la mémoire. Faire 1 changement à la fois et de mesurer son impact.
S'attendre à être déçu et de devoir travailler très dur en effet pour les petites améliorations de performances. Les compilateurs modernes pour la maturité des langages comme Fortran et C sont très, très bon. Si vous avez lu le récit d'un "truc" pour obtenir de meilleures performances de code, garder à l'esprit que le compilateur auteurs ont également lu à ce sujet et, si cela en vaut la peine, probablement mis en œuvre. Ils ont probablement écrit ce que vous avez lu en premier lieu.
&
vs%
pour des puissances de deux (rarement, si jamais, optimisé, mais peuvent avoir d'importantes incidences sur la performance). Si vous lisez un truc pour la performance, la seule façon de savoir si il fonctionne, c'est faire le changement et d'en mesurer l'impact. ne supposez Jamais que le compilateur d'optimiser quelque chose pour vous.n
, gcc remplace% n
avec& (n-1)
même lorsque l'optimisation est désactivé. Ce n'est pas exactement "rarement, voire jamais" ...L'ordre de la traversée de la mémoire peut avoir de profondes répercussions sur les performances et les compilateurs ne sont pas vraiment bien le comprendre et de le corriger. Vous devez être conscient de la localité de cache préoccupations lorsque vous écrivez du code, si vous vous souciez de la performance. Par exemple, les tableaux à deux dimensions en C sont attribués en ligne-grand format. Traversant les tableaux dans la colonne de format majeur tend à faire vous avez plus de défauts de cache et de rendre votre programme plus lié à la mémoire de processeur lié:
-floop-interchange
qui va retourner une intérieure et extérieure de la boucle si l'optimiseur juge rentable.Optimisations Génériques
Ici que quelques uns de mes préférés des optimisations. En fait, j'ai augmenté le temps d'exécution et la réduction du programme de tailles à l'aide de ces.
Déclarer des petites fonctions comme
inline
ou des macrosChaque appel à une fonction (ou méthode) engage des frais généraux, tels que pousser les variables sur la pile. Certaines fonctions peuvent encourir des frais généraux sur le retour ainsi. L'inefficacité de la fonction ou de la méthode a de moins en moins de déclarations dans son contenu que les frais généraux. Ce sont de bons candidats pour l'in-lining, qu'elle soit
#define
macros ouinline
fonctions. (Oui, je saisinline
n'est qu'une suggestion, mais dans ce cas je le considère comme un rappel pour le compilateur.)Enlever les morts et code redondant
Si le code n'est pas utilisé ou ne contribue pas au programme du résultat, se débarrasser d'elle.
Simplifier la conception des algorithmes de
Une fois, j'ai enlevé beaucoup de code assembleur et le temps d'exécution d'un programme en écrivant l'équation algébrique il était en train de calculer et ensuite simplifié l'expression algébrique. La mise en œuvre de la simplification d'expressions algébriques a pris moins de place et de temps que la fonction d'origine.
Déroulement De La Boucle
Chaque boucle a une surcharge de l'incrémentation et la résiliation de la vérification. Pour obtenir une estimation du facteur de performance, de compter le nombre d'instructions dans les frais généraux (minimum de 3: incrément, chèque, goto début de boucle) et diviser par le nombre d'instructions à l'intérieur de la boucle. Plus le nombre est bas mieux c'est.
Edit: de fournir un exemple de déroulement de la boucle
Avant:
Après déroulement:
Dans cet avantage, un avantage secondaire est gagné: plus les instructions sont exécutées avant que le processeur a pour recharger le cache d'instructions.
J'ai eu des résultats étonnants quand j'ai déroulé une boucle de 32 états. Ce fut l'un des goulots d'étranglement depuis que le programme a dû calculer une somme de contrôle sur un fichier de 2 go. Cette optimisation combinée avec bloc de lecture amélioration de la performance à partir de 1 heure à 5 minutes. Déroulement de la boucle fourni d'excellentes performances en langage d'assemblage, mon
memcpy
a été beaucoup plus rapide que le compilateurmemcpy
. -- T. M.Réduction de
if
étatsProcesseurs haine des branches, ou des sauts, car il force le processeur à recharger sa file d'attente d'instructions.
Booléenne de l'Arithmétique (Édité: appliquée format de code de fragment de code, ajout de l'exemple)
Convertir
if
instructions booléennes affectations. Certains processeurs peuvent exécuter conditionnellement instructions sans ramification:La court-circuit de la Logique ET opérateur (
&&
) empêche l'exécution des tests si lestatus
estfalse
.Exemple:
Facteur de Répartition Variable à l'extérieur des boucles de
Si une variable est créée à la volée à l'intérieur d'une boucle, déplacez la création et de répartition à l'avant de la boucle. Dans la plupart des cas, la variable n'a pas besoin d'être attribués lors de chaque itération.
Facteur expressions constantes à l'extérieur des boucles de
Si un calcul ou une valeur de la variable ne dépend pas de l'indice de boucle, le déplacer à l'extérieur (avant) de la boucle.
I/O dans les blocs
Lire et écrire des données dans les grands blocs (blocs). Plus le meilleur. Par exemple, la lecture d'un octect à un moment, c'est moins efficace que la lecture de 1024 octets à lire.
Exemple:
L'efficacité de cette technique peut être confirmé visuellement. 🙂
Ne pas utiliser
printf
famille constant de donnéesConstant de données peut être sortie à l'aide d'un bloc d'écriture. Formaté écrire des déchets d'analyse en temps le texte pour la mise en forme de caractères ou de traitement des commandes de mise en forme. Voir ci-dessus l'exemple de code.
Format de la mémoire, puis d'écrire
Format à un
char
tableau à l'aide de plusieurssprintf
, puis utilisezfwrite
. Cela permet également à la disposition de données pour être cassées en constant "sections" et la variable sections. Pensez à de publipostage.Déclarer la constante de texte (chaîne de caractères littéraux) comme
static const
Lorsque les variables sont déclarées sans le
static
, certains compilateurs peuvent allouer de l'espace sur la pile et de copier les données à partir de la ROM. Ces deux opérations inutiles. Cela peut être résolu en utilisant l'static
préfixe.Enfin, un Code comme le compilateur aurait
Parfois, le compilateur peut optimiser plusieurs petits états de mieux qu'un compliqué que la version. Aussi, l'écriture de code pour aider le compilateur d'optimiser les aide aussi. Si je veux le compilateur à utiliser des bloc d'instructions de transfert, je vais écrire un code qui ressemble, il doit utiliser les instructions spéciales.
fprintf
formats vers une mémoire tampon puis les sorties de la mémoire tampon. Simplifié (pour l'utilisation de la mémoire)fprintf
serait sortie de tout texte sans mise en forme, puis le format de sortie, et répétez jusqu'à ce que l'ensemble de la chaîne de format est traitée, et donc de faire de 1 de sortie pour chaque type de production (formaté vs non formaté). D'autres implémentations aurait besoin d'allouer dynamiquement de la mémoire pour chaque appel à contenir l'intégralité de la nouvelle chaîne (ce qui est mauvais dans les systèmes embarqués de l'environnement). Ma suggestion réduit le nombre de sorties.L'optimiseur n'est pas vraiment le contrôle de la performance de votre programme, vous l'êtes. L'utilisation appropriée d'algorithmes et de structures et d'un profil de profil de, profil de.
Cela dit, vous ne devriez pas l'intérieur de la boucle sur une petite fonction à partir d'un fichier dans un autre fichier, qui cesse d'être insérée.
Éviter de prendre l'adresse d'une variable si possible. Demande d'un pointeur n'est pas "libre", il signifie que la variable doit être gardé en mémoire. Même un tableau peuvent être conservés dans des registres si vous évitez les pointeurs — ce qui est essentiel pour la vectorisation de l'.
Qui nous amène au point suivant, lire la ^#$@ manuel! GCC pouvez vectoriser plaine du code C si vous saupoudrez un
__restrict__
ici et un__attribute__( __aligned__ )
là. Si vous voulez quelque chose de très spécifique de l'optimiseur, vous pourriez avoir à être spécifique.A.c
obtenir incorporé dansB.c
.Sur la plupart des processeurs modernes, le plus gros goulot d'étranglement est la mémoire.
Alias: Charge-Hit-Store peut être dévastateur dans une boucle serrée. Si vous êtes à la lecture d'un emplacement de la mémoire et de l'écriture à l'autre et de savoir qu'ils sont disjoints, soin de mettre un alias de mots clés sur les paramètres de la fonction peut vraiment aider le compilateur de générer plus rapidement le code. Toutefois, si les régions de la mémoire ne se chevauchent et que vous avez utilisé "alias", vous êtes dans une bonne session de débogage de comportements indéfinis!
Cache-miss: Pas vraiment sûr de savoir comment vous pouvez aider le compilateur puisque c'est surtout algorithmique, mais il y a intrinsèques pour extraire de la mémoire.
Aussi, ne pas essayer de convertir les valeurs à virgule flottante en int et vice versa trop car ils utilisent différents registres et de la conversion d'un type vers un autre moyen de l'appel de la conversion de l'instruction, de l'écriture de la valeur de la mémoire et de le lire dans le bon registre.
La grande majorité de code que les gens écrire I/O bound (je crois que tout le code que j'ai écrit pour de l'argent dans les 30 dernières années a été tellement lié), de sorte que les activités de l'optimiseur pour la plupart des gens seront académique.
Cependant, je tiens à rappeler à la population que pour le code soit optimisé, vous avez à dire au compilateur d'optimiser - beaucoup de gens (y compris moi quand j'oublie) post C++ repères ici que sont dénués de sens sans l'optimiseur d'être activé.
utiliser const exactitude, autant que possible, dans votre code. Il permet au compilateur d'optimiser beaucoup mieux.
Dans ce document sont des charges d'autres conseils d'optimisation: RPC optimisations (un peu vieux document si)
faits saillants:
const
etrestrict
qualifié pointeur, cependant, n'est pas défini. Donc un compilateur peut optimiser différemment dans un tel cas.const
sur unconst
de référence ouconst
pointeur vers un non-const
objet bien défini. la modification d'une réelleconst
objet (c'est à dire celui qui a été déclaré commeconst
à l'origine) ne l'est pas.Tenter de programme à l'aide de statique d'attribution unique autant que possible. SSA est exactement le même que vous vous retrouvez avec dans la plupart des langages de programmation fonctionnelle, et c'est ce que la plupart des compilateurs convertir votre code pour faire leurs optimisations car il est plus facile de travailler avec. En faisant cela des lieux où le compilateur peut se confondre sont mis en lumière. Il fait aussi de la tous les, mais le pire, registre des allocateurs de travail aussi bon que le meilleur registre allocateurs, et vous permet de déboguer plus facilement parce que vous n'avez jamais à vous demander où une variable a obtenu sa valeur depuis qu'il n'y avait qu'un seul endroit où il a été affecté.
Éviter les variables globales.
Lorsque vous travaillez avec des données par référence ou pointeur de tirer que dans les variables locales, de faire leur travail, et puis recopier. (sauf si vous avez une bonne raison de ne pas)
Faire usage de la quasi-gratuit de comparaison par rapport à 0 que la plupart des processeurs de vous donner quand à faire des mathématiques ou de la logique des opérations. Vous devrez presque toujours obtenir un drapeau ==0 et <0, à partir de laquelle vous pouvez facilement obtenir 3 conditions:
est presque toujours moins cher que d'autres tests pour d'autres constantes.
Une autre astuce consiste à utiliser la soustraction d'éliminer les comparer dans la gamme de tests.
Ce qui peut souvent éviter un saut dans les langues qui ne court-circuit sur des expressions booléennes et évite le compilateur d'avoir à essayer de comprendre comment gérer les garder
avec le résultat de la première comparaison tout en faisant de la deuxième puis de les combiner.
Cela peut ressembler à elle a le potentiel d'utiliser un supplément de registre, mais il a presque jamais fait. Souvent, vous n'avez pas besoin de foo plus de toute façon, et si vous ne le rc n'est pas encore utilisé de sorte qu'il peut y aller.
Lors de l'utilisation de la chaîne de fonctions en c (strcpy, memcpy, ...), n'oubliez pas qu'ils reviennent -- la destination! Vous pouvez souvent obtenir un meilleur code par "oublier" votre copie du pointeur de destination et il suffit de le saisir de retour de retour de ces fonctions.
Ne jamais oublier la chance de revenir exactement la même chose que la dernière fonction que vous avez appelé retourné. Les compilateurs ne sont pas si grande à ramasser:
Bien sûr, vous pouvez inverser la logique sur que si et seulement un point de retour.
(astuces je l'ai rappelé plus tard)
Déclarer les fonctions comme statique quand vous le pouvez est toujours une bonne idée. Si le compilateur peut prouver à lui-même qu'il a comptabilisé pour chaque appelant d'une fonction particulière, alors il peut briser les conventions d'appel pour cette fonction au nom de l'optimisation. Les compilateurs peuvent souvent éviter de déplacer les paramètres dans les registres ou la pile des positions qui appelle les fonctions s'attendent généralement à ce que leurs paramètres pour être (il a à s'écarter à la fois la fonction appelée et l'emplacement de tous les appelants pour ce faire). Le compilateur peut aussi souvent prendre avantage de savoir ce que la mémoire et les registres de la fonction appelée aurez besoin et d'éviter de générer du code pour préserver les valeurs de la variable qui sont dans les registres ou les emplacements de mémoire qui l'appelle de la fonction ne pas déranger. Cela fonctionne particulièrement bien quand il y a peu d'appels à une fonction. Cela devient beaucoup de l'avantage de l'in-lining code, mais sans réellement inline.
J'ai écrit une optimisation du compilateur C et voici quelques choses utiles à prendre en compte:
Faire la plupart des fonctions statiques. Cela permet interprocedural de la constante de propagation et d'analyse d'alias pour faire son travail, sinon le compilateur a besoin de supposer que la fonction peut être appelée à partir de l'extérieur de l'unité de traduction totalement inconnu des valeurs pour les paramètres. Si vous regardez les bien-connues des bibliothèques open-source-ils tous la marque de fonctions statiques à l'exception de ceux qui ont vraiment besoin d'être extern.
Si les variables globales sont utilisées, marquer statique et constante, si possible. Si ils sont initialisés une fois (en lecture seule), il est préférable d'utiliser une liste d'initialiseur comme static const int VAL[] = {1,2,3,4}, sinon le compilateur ne pourrait pas découvrir que les variables sont en fait des initialisé constantes et ne pourra pas remplacer les charges de la variable avec les constantes.
Ne JAMAIS utiliser un goto à l'intérieur d'une boucle, la boucle ne plus être reconnu par la plupart des compilateurs et aucun des plus importantes optimisations seront appliquées.
Utiliser le pointeur de paramètres uniquement si nécessaire, et de les marquer limiter si possible. Cela permet alias analyse beaucoup parce que le programmeur garanties il n'y a pas d'alias (le interprocedural analyse d'alias est généralement très primitive). Très petite struct objets doivent être passés par valeur et non par référence.
Utiliser des tableaux à la place des pointeurs chaque fois que possible, en particulier à l'intérieur des boucles (a[i]). Un tableau propose habituellement plus d'informations pour l'analyse d'alias et après quelques optimisations du même code sera généré de toute façon (recherche de boucle de réduction de la résistance si curieux). Cela augmente également les chances pour la boucle d'extraction de code invariant à être appliquée.
Essayer de se hisser à l'extérieur de la boucle des appels à de grandes fonctions ou des fonctions externes qui n'ont pas d'effets secondaires (ne dépendent pas de l'itération de la boucle de courant). Les petites fonctions sont dans de nombreux cas, inline ou convertis en intrinsèques qui sont faciles à porter, mais les grandes fonctions peut sembler pour le compilateur d'avoir des effets secondaires lorsqu'ils n'ont pas réellement. Les effets secondaires des fonctions externes sont complètement inconnus, à l'exception de certaines fonctions de la bibliothèque standard, qui sont parfois modélisé par certains compilateurs, faire de la boucle d'extraction de code invariant possible.
Lors de l'écriture des tests avec plusieurs conditions endroit le plus probable de la première. si(a || b || c) devrait être le cas si b || a || c) si b est plus susceptible d'être vraie que les autres. Les compilateurs d'habitude ne sais rien sur les valeurs possibles des conditions et des branches qui sont prises plus (ils ont peut-être connu en utilisant les informations de profil, mais quelques programmeurs de l'utiliser).
À l'aide d'un commutateur est plus rapide que de faire un test comme si(a || b || ... || z). Vérifiez d'abord si votre compilateur le fait automatiquement, certains le font et c'est plus lisible d'avoir la si bien.
Dans le cas des systèmes embarqués et du code écrit en C/C++, j'ai essayer et d'éviter de allocation dynamique de la mémoire autant que possible. La principale raison pour laquelle je fais ce n'est pas nécessairement la performance, mais cette règle générale n'ont d'incidence sur les performances.
Algorithmes utilisés pour gérer le tas sont notoirement lent dans certaines plates-formes (par exemple, vxworks). Pire encore, le temps qu'il faut pour revenir à partir d'un appel à malloc est très dépendante de l'état actuel de la pile. Par conséquent, toute fonction qui appelle la fonction malloc va prendre une performances qui ne peuvent pas être facilement pris en compte. Que les performances peuvent être minimes si le segment est encore propre, mais après que l'appareil fonctionne pendant un temps, les tas peuvent être fragmentés. Les appels vont prendre plus de temps et vous ne pouvez pas calculer facilement la façon dont les performances se dégradent au fil du temps. Vous ne pouvez pas vraiment produire un pire cas estimer. L'optimiseur ne peut pas vous fournir de l'aide dans ce cas. Pour rendre les choses encore pire, si le tas est trop fragmenté, les appels vont commencer à échouer complètement. La solution est d'utiliser des pools de mémoire (par exemple, glib tranches ) au lieu du tas. La répartition des appels vont être beaucoup plus rapide et déterministe si vous le faites à droite.
Une stupide petite astuce, mais celui qui va vous faire économiser une quantité microscopique de vitesse et le code.
Toujours passer les arguments de la fonction dans le même ordre.
Si vous avez f_1(x, y, z) qui appelle f_2, déclarer f_2 comme f_2(x, y, z). Ne pas le déclarer comme f_2(x, z, y).
La raison pour cela est que le C/C++ plate-forme de l'ABI (AKA convention d'appel), promet de passer des arguments, en particulier, les registres et les emplacements de pile. Lorsque les arguments sont déjà dans le bon registres ensuite de ne pas avoir à les déplacer.
Lors de la lecture du code désassemblé j'ai vu quelques ridicule registre de brassage parce que les gens ne suivent pas cette règle.
Deux de codage, la technique je n'ai pas vu dans la liste ci-dessus:
De contournement de l'éditeur de liens par l'écriture de code comme une source unique
Tandis que la compilation séparée est vraiment agréable pour la compilation du temps, c'est très mauvais quand vous parlez de l'optimisation. Fondamentalement, le compilateur ne peut pas optimiser au-delà de la compilation de l'unité, qui est l'éditeur de liens domaine réservé.
Mais si vous la conception de votre programme, vous pouvez peut aussi compiler le biais d'une unique source commune. C'est au lieu de la compilation part1.c et unite2.c est ensuite le lien entre les deux objets, la compilation de tous.c que la simple #include part1.c et unite2.c. Ainsi, vous bénéficiez de toutes les optimisations du compilateur.
C'est très comme écrire les en-têtes des programmes en C++ (et même plus facile de le faire en C).
Cette technique est assez facile si vous écrivez votre programme pour l'activer depuis le début, mais vous devez aussi être conscient de modifier une partie de C sémantique et vous pouvez rencontrer certains problèmes comme des variables statiques ou macro collision. Pour la plupart des programmes, il est assez facile de surmonter les petits problèmes qui se produit. Sachez également que la compilation d'une source unique est beaucoup plus lent et peut prend une énorme quantité de mémoire (généralement pas un problème avec les systèmes modernes).
À l'aide de cette technique simple, il m'est arrivé de faire quelques programmes que j'ai écrit dix fois plus vite!
Comme le registre de mot-clé, cette astuce pourrait devenir obsolètes rapidement. L'optimisation par le biais de l'éditeur de liens commencent à être pris en charge par les compilateurs gcc: Lien à l'optimisation du temps.
Séparer les tâches élémentaires dans les boucles
Celui-ci est plus délicate. C'est à propos de l'interaction entre la conception d'un algorithme et de la façon dont l'optimiseur de gérer le cache et l'allocation de registres. Assez souvent, les programmes en boucle sur certaines de structure de données et pour chaque élément d'effectuer certaines actions. Assez souvent, les actions effectuées peuvent être répartis entre deux logiquement tâches indépendantes. Si c'est le cas, vous pouvez écrire exactement le même programme avec deux boucles sur le même limite effectuer exactement une tâche. Dans certains cas, l'écriture de cette façon peut être plus rapide que la boucle unique (les détails sont plus complexes, mais une explication peut être que la simple tâche cas, toutes les variables peuvent être conservés dans les registres du processeur et avec la plus complexe, il n'est pas possible et certains registres doivent être écrites dans la mémoire et les relire plus tard et le coût est plus élevé que d'autres le contrôle de flux).
Être prudent avec celui-ci (profil de performances à l'aide de ce truc ou pas) comme à l'aide de registre, il peut ainsi donner la moindre des représentations que les cultivars améliorés.
Fait, j'ai vu cette façon de faire dans SQLite et ils prétendent que c'résultats au niveau des performances stimule ~5%: Mettre votre code dans un fichier ou d'utiliser le préprocesseur pour faire l'équivalent de ce. De cette façon, l'optimiseur va avoir accès à l'ensemble du programme et peuvent faire plus interprocedural optimisations.
-O3
- il fustigé 22% de la taille d'origine de mon programme. (C'est pas le CPU, donc je n'ai pas beaucoup à dire sur la vitesse.)Plus les compilateurs modernes devraient faire un bon travail d'accélérer la queue de la récursivité, parce que les appels de fonction peut être optimisé à.
Exemple:
Bien entendu, cet exemple n'ont pas de vérification des limites.
La Fin De Modifier
Alors que je n'ai pas de connaissance directe du code; il semble clair que les exigences de l'utilisation d'expressions de table communes sur SQL Server a été spécialement conçu afin qu'il puisse optimiser via queue de la récursivité.
Ne pas faire le même travail, encore et encore!
Une commune antipattern que je vois remonte le long de ces lignes:
Le compilateur a fait appel à toutes les fonctions de tous les temps. En supposant que vous avez, le programmeur, sait que l'ensemble de l'objet ne change pas au cours de ces appels, pour l'amour de tout ce qui est saint...
Dans le cas de l'singleton getter les appels ne peuvent pas être trop coûteux, mais il est certainement un coût (généralement, "vérifiez pour voir si l'objet a été créé, s'il n'a pas, le créer, puis de le retourner). Le plus compliqué de cette chaîne de getters devient, plus de temps perdu, nous allons avoir.
Utilisation la plus locale possible pour toutes les déclarations de variables.
Utilisation
const
chaque fois que possibleNe utilise le registre, sauf si vous prévoyez de profil à la fois avec et sans qu'il
Les 2 premiers de ceux-ci, surtout #1-on aider l'optimiseur d'analyser le code. Il en sera notamment ainsi de l'aider à faire les bons choix sur les variables à conserver dans les registres.
Aveuglément à l'aide du mot-clé de registre n'est plus susceptible d'aider le plus de tort à votre optimisation, Il est tout simplement trop difficile de savoir ce qui va de la matière jusqu'à ce que vous regardez à l'assemblée de la sortie ou de profil.
Il y a d'autres choses qui sont importantes pour l'obtention de bonnes performances de code, la conception de vos structures de données afin de maximiser la cohérence du cache par exemple. Mais la question était à propos de l'optimiseur.
Alignez vos données natif/limites naturelles.
M'a rappelé quelque chose que j'ai rencontré une fois, où le symptôme est tout simplement que nous avons été à court de mémoire, mais le résultat a été sensiblement augmenté la performance (ainsi que d'énormes réductions dans la mémoire de l'empreinte).
Le problème dans ce cas est que le logiciel nous avons été en utilisant des tonnes de peu d'allocations. Comme, l'affectation de quatre octets ici, six octets, etc. Beaucoup de petits objets, aussi, de l'exécution dans les 8-12 plage d'octets. Le problème n'était pas tellement que le programme a besoin de beaucoup de petites choses, c'est qu'il a consacré beaucoup de petites choses individuellement, ce qui pléthorique chaque allocation pour (sur cette plate-forme particulière) 32 octets.
Partie de la solution était de mettre en place un Alexandrescu-style petit objet de la piscine, mais l'étendre afin que je puisse allouer des tableaux de petits objets ainsi que des éléments individuels. Cela a contribué grandement à la performance ainsi depuis plus d'éléments sont présents dans le cache à tout moment.
L'autre partie de la solution était de remplacer l'utilisation généralisée de manuellement géré char* membres avec une authentification unique (petit-optimisation de la chaîne) chaîne de caractères. L'allocation minimale étant de 32 octets, j'ai construit une chaîne de classe qui avait embarqué 28-tampon de caractère derrière un char*, donc 95% de nos chaînes n'ont pas besoin de faire une allocation supplémentaire (et puis j'ai manuellement remplacé presque chaque aspect de char* dans cette bibliothèque avec cette nouvelle classe, c'était amusant ou pas). Cela a contribué à une tonne avec une fragmentation de la mémoire, qui a ensuite augmenté la localité de référence pour les autres pointue d'objets, et de même il y avait des gains de performances.
Une technique soignée, j'ai appris à partir d' @MSalters commentaire sur cette réponse permet de compilateurs à faire de copie élision même lors du retour des objets différents selon qu'une condition:
Si vous avez des petites fonctions vous appelez à plusieurs reprises, dans le passé j'ai eu des gains importants en les mettant dans des en-têtes comme "static inline". Les appels de fonction sur la ix86 sont étonnamment cher.
De réimplanter des fonctions récursives en une façon non-récursive à l'aide d'une pile explicite pouvez aussi gagner beaucoup, mais alors vous êtes vraiment dans le domaine du temps de développement vs gain.
Voici mon deuxième morceau de l'optimisation des conseils. Comme avec mon premier conseil c'est d'usage général, de la langue ou d'un processeur spécifique.
Lire le compilateur manuel attentivement et de comprendre ce qu'il vous dit. Utilisez le compilateur à un maximum.
Je suis d'accord avec un ou deux autres répondants qui ont identifié la sélection de l'algorithme de droite comme critique à la pression de la performance d'un programme. Au-delà, les taux de rendement (mesuré à l'exécution du code de l'amélioration, sur le temps que vous investissez en utilisant le compilateur est beaucoup plus élevé que le taux de retour en optimisant le code.
Oui, compilateur auteurs ne sont pas d'une course de codage des géants et des compilateurs contenir des erreurs et ce qui devrait, selon le manuel et selon la théorie des compilateurs, rendre les choses plus vite rend parfois les choses plus lentement. C'est pourquoi vous devez prendre une étape à la fois et de mesurer avant et de l'après-tweak performance.
Et oui, en fin de compte, vous pourriez être confronté à une explosion combinatoire des drapeaux de compilation si vous avez besoin d'avoir un script ou deux pour courir faire avec différents drapeaux du compilateur, la file d'attente des travaux sur le cluster de grande taille et de rassembler, le temps d'exécution de la statistique. Si c'est juste vous et Visual Studio sur un PC, vous serez à court d'intérêt à long avant que vous avez essayé assez de combinaisons de suffisamment de drapeaux du compilateur.
Ce qui concerne
Marque
Quand j'ai d'abord ramasser un morceau de code que j'pouvez généralement obtenir un facteur de 1,4 -- 2,0 fois plus de performance (c'est à dire la nouvelle version du code s'exécute en 1/1.4 1/2 ou du temps de l'ancienne version) dans un jour ou deux en jouant avec les drapeaux de compilation. D'accord, cela peut être un commentaire sur le manque de compilateur savvy parmi les scientifiques qui proviennent en grande partie du code je travaille, plutôt que le symptôme de mon excellence. Avoir mis les drapeaux de compilation pour max (et c'est rarement juste -O3), il peut prendre des mois de travail acharné pour obtenir un autre facteur de 1,05 1,1
Lors de DÉCEMBRE est sorti avec ses processeurs alpha, il y avait une recommandation de conserver le nombre d'arguments d'une fonction sous 7, comme le compilateur pourrait toujours essayer de mettre jusqu'à 6 arguments dans les registres automatiquement.
De performance, de mettre l'accent d'abord sur l'écriture de code maintenable - composants, faiblement couplé, etc, de sorte que lorsque vous avez à isoler une partie, soit à la réécriture, optimiser ou tout simplement de profil, vous pouvez le faire sans trop d'effort.
Optimiseur d'aide de votre programme de performances légèrement.
Vous êtes l'obtention de bonnes réponses ici, mais ils supposent que votre programme est très proche de l'optimal pour commencer, et vous dire
Dans mon expérience, un programme peut être écrit correctement, mais cela ne signifie pas qu'il est proche de l'optimal. Il faut plus de travail pour arriver à ce point.
Si je peux donner un exemple, cette réponse montre comment est parfaitement raisonnable-programme de recherche a été faite 40 fois plus rapide par macro-optimisation. Grande vitesse ne peut pas être fait dans chaque programme de premier écrit, mais dans de nombreux (sauf pour les très petits programmes), il peut, dans mon expérience.
Après que c'est fait, micro-optimisation (des hot-spots) peut vous donner une récompense.
je utiliser le compilateur intel. sur Windows et Linux.
quand plus ou moins fait que j'ai le profil du code. puis les accrocher sur les points chauds et d'essayer de changer le code pour permettre compilateur de faire un meilleur travail.
si un code est un calcul un et contiennent un grand nombre de boucles de vectorisation rapport à intel compilateur est très utile - rechercher les vec-rapport " dans l'aide.
donc l'idée principale - polonais de la critique pour les performances du code. comme pour le reste - la priorité à l'être correct et facile à entretenir - court fonctions, un code clair, qui pourrait être compris 1 an plus tard.
Une optimisation que j'ai utilisé en C++ est la création d'un constructeur qui ne fait rien. On doit manuellement l'appel d'une fonction init() pour mettre l'objet dans un état de fonctionnement.
Cela a l'avantage dans le cas où j'ai besoin d'un grand vecteur de ces classes.
J'appelle de la réserve() pour allouer de l'espace pour le vecteur, mais le constructeur n'a pas fait de toucher la page de la mémoire de l'objet. J'ai donc passé un certain espace d'adresses, mais pas réellement consommé beaucoup de mémoire physique. - Je éviter les défauts de page associé la associé à des coûts de construction.
Comme je l'générer des objets pour remplir le vecteur, je les ai à l'aide de la fonction init(). Cela limite le nombre total de mes défauts de page, et évite la nécessité de redimensionner() le vecteur tout en la remplissant.
std::vector
sur cette machine - pas un morceau de conseil qui est généralement applicable, et il pourrait être dangereux (par exemple, si certains lecteur naïvement suppose qu'ils doivent utiliser init fonctions de partout 😉Une chose que j'ai fait c'est d'essayer de garder les actions coûteuses pour les endroits où l'utilisateur pourrait s'attendre à ce que le programme de retarder un peu. La performance globale est liée à la réactivité, mais ce n'est pas tout à fait la même, et pour beaucoup de choses, la réactivité est le plus important de la performance.
La dernière fois que j'ai eu à faire des améliorations dans la performance globale, j'ai gardé un œil sur des algorithmes sous-optimaux, et de chercher des endroits qui sont susceptibles d'avoir des problèmes de cache. Je profilé et mesuré les performances de la première, et de nouveau après chaque modification. Ensuite, l'entreprise s'est effondré, mais il est intéressant et instructif de travail de toute façon.
J'ai longtemps soupçonné, mais jamais prouvé que la déclaration des rangées de manière à maintenir une puissance de 2, comme le nombre d'éléments, permet à l'optimiseur de faire un réduction de la résistance en remplaçant une multiplication par un passage par un certain nombre de bits, lors de la recherche des éléments individuels.
val * 7
tourné dans ce qui serait autrement ressembler(val << 3) - val
.Mettre des petits et/ou fréquemment appelées les fonctions en haut du fichier source. Qui rend plus facile pour le compilateur de trouver des opportunités pour inline.
L'une des choses dont je me souviens vaguement de cobol dans les années 80, était qu'il y avait des options du linker qui vous a permis d'effet de l'ordre dans lequel les fonctions sont liées. Cela vous a permis de vous (peut-être) l'augmentation le code de localité.
Le long de cette même idée. Si ont peut se demander si une optimisation possible pourrait être obtenu en utilisant le modèle
La pour la tête, et ifs, peut s'insérer dans un cache de bloc,
qui, en théorie, pourrait conduire à une accélération de l'exécution de la boucle.
Je suppose que la elses étant similaire pourrait être optimisée à un certain degré.
Commentaires?
Suis-je en train de rêver?
Laisser l'optimiseur de faire son travail.
Au sérieux. Ne pas essayer de déjouer l'optimiseur. Il a été conçu par des gens brillants avec beaucoup, beaucoup plus d'expérience que vous.