116 votes

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 a 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 l'alias de 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.

Voici 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?

[Edit] Décalage lien

76voto

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 n'importe trucs astucieux 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.

54voto

celion Points 2446

Écrire pour les 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 à

void DoSomething(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
    for (int i=0; i<numFoo, i++)
    {
         barOut.munge(foo1, foo2[i]);
    }
}

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 le restreindre ed pointeurs, mais il est tout aussi efficace (et beaucoup plus claire) pour ce faire:

void DoSomethingFaster(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
    Foo barTemp = barOut;
    for (int i=0; i<numFoo, i++)
    {
         barTemp.munge(foo1, foo2[i]);
    }
    barOut = barTemp;
}

Il semble stupide, mais le compilateur peut être beaucoup plus intelligent de traiter avec la variable locale, car il ne peut pas chevauchement possible 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).

47voto

vicatcu Points 2583

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é:

#define N 1000000;
int matrix[N][N] = { ... };

//awesomely fast
long sum = 0;
for(int i = 0; i < N; i++){
  for(int j = 0; j < N; j++){
    sum += matrix[i][j];
  }
}

//painfully slow
long sum = 0;
for(int i = 0; i < N; i++){
  for(int j = 0; j < N; j++){
    sum += matrix[j][i];
  }
}

36voto

Thomas Matthews Points 19838

Optimisations Génériques

Ici, comme certains de mes favoris 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 macros

Chaque 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. 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, que ce soit en tant que #define des macros ou inline fonctions. (Oui, je sais, inline n'est qu'une suggestion, mais dans ce cas, je considère cela 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

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: fournir un exemple de déroulement de la boucle Avant:

unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
    sum += *buffer++;
}

Après déroulement:

unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
    sum += *buffer++; // 1
    sum += *buffer++; // 2
    sum += *buffer++; // 3
    sum += *buffer++; // 4
    sum += *buffer++; // 5
    sum += *buffer++; // 6
    sum += *buffer++; // 7
    sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
    sum += *buffer++;
}

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 compilateur memcpy. -- T. M.

Réduction de if des déclarations

Les processeurs de la haine des branches, ou des sauts, car il force le processeur à recharger sa file d'attente d'instructions.

Boolean Arithmétique (Édité: appliquer le code de format de fragment de code, ajout de l'exemple)

Convertir if instructions booléennes affectations. Certains processeurs peuvent exécuter conditionnellement instructions sans ramification:

bool status = true;
status = status && /* first test */;
status = status && /* second test */;

Le court-circuitage de la Logique ET de l'opérateur (&&) empêche l'exécution des tests si l' status est false.

Exemple:

struct Reader_Interface
{
  virtual bool  write(unsigned int value) = 0;
};

struct Rectangle
{
  unsigned int origin_x;
  unsigned int origin_y;
  unsigned int height;
  unsigned int width;

  bool  write(Reader_Interface * p_reader)
  {
    bool status = false;
    if (p_reader)
    {
       status = p_reader->write(origin_x);
       status = status && p_reader->write(origin_y);
       status = status && p_reader->write(height);
       status = status && p_reader->write(width);
    }
    return status;
};

Le 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 d'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:

static const char  Menu_Text[] = "\n"
    "1) Print\n"
    "2) Insert new customer\n"
    "3) Destroy\n"
    "4) Launch Nasal Demons\n"
    "Enter selection:  ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);

L'efficacité de cette technique peut être confirmé visuellement. :-)

Ne pas utiliser printf de la famille constant de données

Constant 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.

Le Format de la mémoire, puis d'écrire

Format à un char tableau à l'aide de plusieurs sprintf, puis utilisez fwrite. Cela permet également à la disposition de données pour être cassées en constant "sections" et la variable sections. Pense de publipostage.

Déclarer la constante de texte (chaîne de caractères littéraux) 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

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.

26voto

Potatoswatter Points 70305

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.

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.

Ce qui nous amène au point suivant, lire la ^#$@ manuel! GCC pouvez vectoriser plaine du code C si vous saupoudrez un __restrict__ et __attribute__( __aligned__ ) . Si vous voulez quelque chose de très spécifique de l'optimiseur, vous pourriez avoir à être précis.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X