Méthodes conseillées pour l'optimisation
Mise à jour : novembre 2007
Ce document décrit certaines des meilleures pratiques pour l'optimisation dans Visual C++ 2005. Les sujets abordés sont les suivants :
Options du compilateur et de l'éditeur de liens
Changements dans Visual C++ 2005
Quel niveau d'optimisation dois-je utiliser ?
Commutateurs à virgule flottante
Optimisation des declspecs
Optimisation des pragmas
__restrict et __assume
Prise en charge intrinsèque
Exceptions
Options du compilateur et de l'éditeur de liens
Changements dans Visual C++ 2005
Visual C++ 2005 prend désormais en charge l'optimisation guidée par profil (PGO, Profil-Guided Optimization). Cette optimisation utilise les données de profil des exécutions antérieures d'une version instrumentée d'une application pour exécuter une optimisation ultérieure de l'application. Étant donné que l'optimisation PGO peut prendre du temps, elle n'est pas très utilisée par les développeurs ; toutefois, nous vous recommandons de l'utiliser pour la version release finale d'un produit. Pour plus d'informations, consultez Optimisations guidées par profil.
En outre, l'optimisation de l'ensemble du programme (également appelée Génération de code durant l'édition de liens) et les optimisations /O1 et /O2 ont été améliorées. En général, une application compilée avec l'une de ces options s'exécute plus rapidement que la même application compilée avec un compilateur antérieur.
Pour plus d'informations, consultez /GL (Optimisation de l'ensemble du programme) et /O1, /O2 (Réduire la taille, augmenter la vitesse).
Quel niveau d'optimisation dois-je utiliser ?
Si possible, les versions release finales doivent être compilées avec les optimisations guidées par profil. Si la génération avec l'optimisation PGO n'est pas possible, en raison d'une infrastructure insuffisante pour exécuter les versions instrumentées ou d'un accès non autorisé aux scénarios, nous vous suggérons d'exécuter la génération avec l'optimisation de l'ensemble du programme.
Le commutateur /Gy est également très utile. Il génère un COMDAT distinct pour chaque fonction, en offrant plus de flexibilité à l'éditeur de liens pour supprimer les COMDAT non référencés et le repli COMDAT. L'utilisation de /Gy présente un seul inconvénient : il peut produire un effet mineur au moment de la génération. Par conséquent, il est généralement recommandé de l'utiliser. Pour plus d'informations, consultez /Gy (Activer la liaison au niveau des fonctions).
Pour la liaison dans les environnements 64 bits, il est recommandé d'utiliser l'option de l'éditeur de liens /OPT:REF,ICF, et dans les environnements 32 bits, /OPT:REF est conseillé. Pour plus d'informations, consultez /OPT (Optimisations).
Il est également vivement recommandé de générer des symboles de débogage, même avec les versions release optimisées. Cela n'affecte pas le code généré, et simplifie considérablement le débogage de votre application, si besoin est.
Commutateurs à virgule flottante
Dans Visual C++ 2005, l'option de compilateur /Op a été supprimée et les quatre options de compilateur ci-après qui utilisent les optimisations à virgule flottante ont été ajoutées :
/fp:precise |
Recommandation par défaut qui doit être utilisée dans la plupart des cas. |
/fp:fast |
Recommandé si les performances ont une importance majeure, par exemple dans les jeux. On obtient des performances plus rapides. |
/fp:strict |
Recommandé si des exceptions de virgule flottante précises et un comportement IEEE sont souhaités. On obtient des performances plus lentes. |
/fp:except[-] |
Peut être utilisé conjointement avec /fp:strict ou /fp:precise, mais pas /fp:fast. |
Pour plus d'informations, consultez /fp (Spécifier le comportement de virgule flottante).
Optimisation des declspecs
Cette section présente deux declspecs qui peuvent être utilisés dans les programmes pour améliorer les performances : __declspec(restrict) et __declspec(noalias).
Le declspec restrict peut être appliqué uniquement aux déclarations de fonction qui retournent un pointeur, par exemple __declspec(restrict) void *malloc(size_t size);
Le declspec restrict est utilisé sur les fonctions qui retournent des pointeurs sans alias. Ce mot clé est utilisé pour l'implémentation de la bibliothèque runtime C de malloc puisqu'il ne retourne jamais une valeur de pointeur déjà utilisée dans le programme en cours (sauf si vous effectuez une opération non autorisée, par exemple l'utilisation de la mémoire après sa libération).
Le declspec restrict fournit plus d'informations au compilateur pour exécuter des optimisations de compilateur. La détermination des pointeurs qui définissent un alias sur d'autres pointeurs est l'une des opérations les plus difficiles pour un compilateur, et l'utilisation de ces informations aide considérablement le compilateur.
Il est important de noter qu'il s'agit d'une promesse faite au compilateur, et non une information que le compilateur vérifie. Si votre programme utilise ce declspec restrict de manière inappropriée, un comportement incorrect risque de se produire.
Pour plus d'informations, consultez restrict.
Le declspec noalias est également appliqué uniquement aux fonctions, et indique que la fonction est une fonction semi-pure. Une fonction semi-pure est une fonction qui référence ou modifie uniquement des variables locales, des arguments et des indirections d'arguments de premier niveau. Ce declspec est une promesse faite au compilateur, et si la fonction référence des globales ou des indirections d'arguments de pointeur de second niveau, le compilateur peut générer du code qui arrête l'application.
Pour plus d'informations, consultez noalias.
Optimisation des pragmas
Plusieurs pragmas utiles permettent également d'optimiser le code. Le premier pragma que nous allons présenter est le pragma #pragma optimize :
#pragma optimize("{opt-list}", on | off)
Ce pragma vous permet de définir un niveau d'optimisation donné, fonction par fonction. Il est idéal pour les occasions rares où votre application tombe en panne lorsqu'une fonction donnée est compilée avec l'optimisation. Vous pouvez l'utiliser pour désactiver les optimisations d'une fonction unique :
#pragma optimize("", off)
int foo() {...}
#pragma optimize("", on)
Pour plus d'informations, consultez optimize.
La fonctionnalité inline est l'une des optimisations les plus importantes exécutées par le compilateur. Nous allons présenter deux pragmas qui permettent de modifier ce comportement.
#pragma inline_recursion est utile pour spécifier si vous souhaitez ou non que l'application traite en mode inline un appel récurrent. Il est désactivé par défaut. Vous pouvez l'activer pour la récursivité superficielle de petites fonctions. Pour plus d'informations, consultez inline_recursion.
Un autre pragma utile pour limiter la profondeur de la fonctionnalité inline est #pragma inline_depth. Il est généralement utile dans les situations où vous essayez de réduire la taille d'un programme ou d'une fonction. Pour plus d'informations, consultez inline_depth.
__restrict et __assume
Deux mots clés dans Visual C++ 2005 permettent d'améliorer les performances : __restrict et __assume.
Tout d'abord, il faut noter que __restrict et __declspec(restrict) sont deux éléments distincts. Bien qu'ils soient apparentés, leurs sémantiques sont différentes. __restrict est un qualificateur de type, comme const ou volatile, mais exclusivement pour les types pointeur.
Un pointeur modifié avec __restrict est appelé un pointeur __restrict. Un pointeur __restrict est un pointeur qui est uniquement accessible par le biais du pointeur __restrict. En d'autres termes, un autre pointeur ne peut pas être utilisé pour accéder aux données pointées par le pointeur __restrict.
__restrict peut être un outil performant pour l'optimiseur Visual C++, mais vous devez l'utiliser avec précaution. S'il est utilisé de manière incorrecte, l'optimiseur peut exécuter une optimisation qui arrête votre application.
Le mot clé __restrict dans Visual C++ 2005 remplace le commutateur /Oa des versions antérieures.
__assume figurait dans plusieurs versions de Visual C++, mais son utilisation a été optimisée dans Visual C++ 2005. Avec __assume,, un développeur peut demander au compilateur de faire des hypothèses sur la valeur d'une variable.
Par exemple, __assume(a < 5); indique à l'optimiseur que la variable a est inférieure à 5 dans une ligne de code donnée. Il s'agit également d'une promesse faite au compilateur. Si a a en réalité la valeur 6 à ce stade du programme, le comportement du programme après l'optimisation par le compilateur peut ne pas correspondre au résultat prévu. __assume est très utile avant les instructions switch et/ou les expressions conditionnelles.
Des restrictions sont appliquées à __assume. Tout d'abord, à l'instar de __restrict, il s'agit uniquement d'une suggestion ; le compilateur peut l'ignorer. De même, __assume ne fonctionne actuellement qu'avec les inégalités variables par rapport aux constantes. Il ne propage pas des inégalités symboliques, par exemple, assume(a < b).
Prise en charge intrinsèque
Les éléments intrinsèques sont des appels de fonction où le compilateur a une connaissance intrinsèque sur l'appel et émet un code pour cette fonction au lieu d'appeler une fonction dans une bibliothèque. La prise en charge des éléments intrinsèques a été sensiblement améliorée dans Visual C++ 2005. Le fichier d'en-tête intrin.h présent dans <Installation_Directory>\VC\include\intrin.h contient tous les éléments intrinsèques disponibles pour chacune des trois plateformes prises en charge (x86, x64 et Itanium).
Les éléments intrinsèques permettent au programmeur de sonder le code sans utiliser l'assembly. L'utilisation des éléments intrinsèques présente plusieurs avantages :
Votre code est plus portable. Plusieurs des éléments intrinsèques sont disponibles sur plusieurs architectures d'UC.
Votre code est plus facile à lire, car il est toujours écrit dans C/C++.
Votre code tire profit des optimisations du compilateur. Comme le compilateur se porte mieux, la génération du code pour les éléments intrinsèques est améliorée.
Pour plus d'informations, consultez Compiler Intrinsics et Benefits of Using Intrinsics.
Exceptions
L'utilisation des exceptions provoque une altération des performances. Des restrictions sont appliquées lors de l'utilisation de blocs try qui interdisent au compilateur d'exécuter certaines optimisations. Sur les plateformes x86, on constate une altération supplémentaire des performances des blocs try en raison d'informations d'état supplémentaires qui doivent être générées pendant l'exécution du code. Sur les plateformes 64 bits, les blocs try n'altèrent pas autant les performances, mais lorsqu'une exception est levée, le processus de recherche du gestionnaire et de déroulement de la pile peut s'avérer coûteux.
Par conséquent, il est recommandé de ne pas utiliser de blocs try/catch dans le code qui ne sont pas vraiment utiles. Si vous devez utiliser des exceptions, utilisez si possible des exceptions synchrones. Pour plus d'informations, consultez Structured Exception Handling (C++).
Enfin, levez des exceptions uniquement dans des cas exceptionnels. L'utilisation des exceptions pour le flux de contrôle général risque de provoquer une altération des performances.