Optimisations
Non spécifique au microprocesseur
Les sections suivantes décrivent les optimisations générales effectuées par le compilateur, elles ne sont pas spécifiques au microprocesseur. Certains d'entre eux nécessitent un remplacement de commutateur du compilateur tandis que d'autres sont effectués automatiquement (ceux nécessitant un commutateur seront notés comme tels).
Simplification de sous-expressions constant
En Free Pascal, si le ou les opérandes d'un opérateur sont des constantes, ils seront évalués au moment de la compilation.
Exemple :
- X:=1+2+3+6+5;
générera le même code que :
- X:=17;
De plus, si un index de tableau est une constante, le déplacement sera évalué au moment de la compilation. Cela signifie que l'accès à MyData[5] est aussi efficace que l'accès à une variable normale.
Enfin, l'appel des fonctions Chr, Hi, Lo, Ord, Pred ou Succ avec des paramètres constants ne génère aucun appel à la bibliothèque d'exécution ; les valeurs sont évaluées au moment de la compilation.
Fusion de constante
L'utilisation de la même chaîne constante, de la même valeur à virgule flottante ou du même ensemble de constantes deux fois ou plus génère une seule copie de cette constante.
Évaluation de raccourcie
L'évaluation de l'expression booléenne s'arrête dès que le résultat est connu, ce qui accélère l'exécution du code par rapport à si tous les opérandes booléens étaient évalués.
Constante d'ensemble
L'utilisation de l'opérateur in est toujours plus efficace que l'utilisation des opérateurs équivalents <>, =, <=, >=, < et >. En effet, les comparaisons de l'intervalle peuvent être effectuées plus facilement avec l'opérateur in qu'avec les opérateurs de comparaison normaux.
Petits ensembles
Les ensembles contenant moins de 33 éléments peuvent être directement codés en utilisant une valeur de 32 bits, donc aucun appel à la bibliothèque d'exécution pour évaluer les opérandes sur ces ensembles n'est requis ; ils sont directement codés par le générateur de code.
Vérification de la portée
Les affectations de constantes aux variables sont vérifiées au moment de la compilation, ce qui élimine le besoin de générer un code de vérification de l'intervalle d'exécution.
Et au lieu de modulo
Lorsque le deuxième opérande d'un mod sur une valeur non signée est une puissance constante de 2, une instruction de Et binaire est utilisée à la place d'une division entière. Cela génère un code plus efficace.
Décalage au lieu de multiplier ou de diviser
Lorsque l'un des opérandes d'une multiplication est une puissance de deux, ils sont codés à l'aide d'instructions de décalage arithmétique, ce qui génère un code plus efficace (utilise moins de cycle d'horloge d'un microprocesseur).
De même, si le diviseur dans une opération div est une puissance de deux, il est codé à l'aide d'instructions de décalage arithmétique.
Il en va de même lors de l'accès à des index de tableau étant des puissances de deux, l'adresse est calculée à l'aide de décalages arithmétiques au lieu de l'instruction de multiplication.
Alignement automatique
Par défaut, toutes les variables supérieures à un octet sont garanties d'être alignées au moins sur une limite de mot. L'alignement sur la pile et dans la section de données dépend du microprocesseur.
Liaison intelligente
Cette fonctionnalité supprime tout le code non référencé dans le fichier exécutable final, ce qui rend le fichier exécutable beaucoup plus petit.
La liaison intelligente est activée avec le commutateur de ligne de commande -CX ou à l'aide de la directive globale {$SMARTLINK ON}.
Routines en ligne
Les routines de bibliothèque d'exécution suivantes sont codées directement dans l'exécutable final : Lo, Hi, High, SizeOf, TypeOf, Length, Pred, Succ, Inc, Dec et Assigned.
Omission du cadre de pile
Dans des conditions spécifiques, le cadre de pile sera omis et la variable sera directement accessible via le pointeur de pile.
Conditions d'omission d'un cadre de pile :
- Le microprocesseur cible est x86 ou ARM.
- Le commutateur de ligne de commande -O2 ou -OoSTACKFRAME doit être spécifié.
- Aucun assembleur en ligne n'est utilisé.
- Aucune exception n'est utilisée.
- Aucune routine n'est appelée avec des paramètres sortants sur la pile.
- La fonction n'a aucun paramètre.
Enregistrer les variables
Lors de l'utilisation du commutateur -Or, les variables ou paramètres locaux étant très souvent utilisés seront déplacés vers les registres pour un accès plus rapide.
Spécifique au microprocesseur
Ceci répertorie les optimisations de bas niveau effectuées, microprocesseur par microprocesseur.
Spécifique au Intel 80x86
Voici une liste des techniques d'optimisation utilisées dans le compilateur :
- Lors de l'optimisation pour un processeur spécifique (-Op1, -Op2, -Op3, les opérations suivantes sont effectuées :
- Dans les instructions case, une vérification est effectuée si une table de sauts ou une séquence de sauts conditionnels doit être utilisée pour des performances optimales.
- Détermine un certain nombre de stratégies lors de l'optimisation des judas, par exemple : movzbl (%ebp), %eax sera remplacé par xorl %eax,%eax ; movb (%ebp),%al pour Pentium et Pentium MMX.
- Lors de l'optimisation de la vitesse (-OG, la valeur par défaut) ou de la taille (-Os), un choix est fait entre l'utilisation d'instructions plus courtes (pour la taille) telles que saisir $4, ou des instructions plus longues subl $4,%esp pour la vitesse. Lorsqu'une taille plus petite est demandée, les données sont alignées sur des limites minimales. Lorsque la vitesse est demandée, les données sont alignées autant que possible sur les limites les plus efficaces.
- Optimisations rapides (-O1) : activez l'optimiseur de judas.
- Optimisations plus lentes (-O2) : activez également l'élimination des sous-expressions communes (anciennement appelée «optimiseur de rechargement»).
- Optimisations incertaines (-OoUNCERTAIN) : Avec ce commutateur, l'algorithme d'élimination de sous-expression commune peut être forcé à effectuer des optimisations incertaines. Bien que vous puissiez activer des optimisations incertaines dans la plupart des cas, pour les personnes ne comprenant pas l'explication technique suivante, il peut être plus sûr de les laisser désactivées.
Remarque : Si des optimisations incertaines sont activées, l'algorithme CSE suppose que :
- Si quelque chose est écrit dans un registre local/global ou dans un paramètre de procédure/fonction, cette valeur n'écrase pas la valeur vers laquelle pointe un pointeur.
- Si quelque chose est écrit dans la mémoire pointée par une variable pointeur, cette valeur n'écrase pas la valeur d'une variable locale/globale ou d'un paramètre de procédure/fonction.
Le résultat pratique est que vous ne pouvez pas utiliser les optimisations incertaines si vous écrivez et lisez des variables locales ou globales directement et via des pointeurs (cela inclut les paramètres Var, car ce sont également des pointeurs).
L'exemple suivant produira du mauvais code lorsque vous activerez des optimisations incertaines :
La raison pour laquelle il produit du mauvais code est que vous accédez à la variable globale Temp à la fois via son nom Temp et via un pointeur, dans ce cas en utilisant le paramètre de variable _Bar, n'étant rien d'autre qu'un pointeur vers Temp dans le code ci-dessus.
D'un autre côté, vous pouvez utiliser les optimisations incertaines si vous accédez à des variables ou paramètres globaux/locaux via des pointeurs, et y accédez uniquement via ce pointeur.
Par exemple :
- Type
- TMyRec=Record
- a,b:LongInt;
- End;
- PMyRec=^TMyRec;
- TMyRecArray=Array[1..100000]of TMyRec;
- PMyRecArray=^TMyRecArray;
-
- Var
- MyRecArrayPtr:PMyRecArray;
- MyRecPtr:PMyRec;
- Counter:LongInt;
-
- BEGIN
- New(MyRecArrayPtr);
- For Counter:=1 to 100000 do Begin
- MyRecPtr:=@MyRecArrayPtr^[Counter];
- MyRecPtr^.a:=Counter;
- MyRecPtr^.b:=Counter div 2;
- End;
- END.
Produira un code correct, car la variable globale MyRecArrayPtr n'est pas accessible directement, mais uniquement via un pointeur (MyRecPtr dans ce cas).
En conclusion, on pourrait dire que l'on ne peut utiliser des optimisations incertaines que lorsqu'on sait ce que l'on fait.
Spécifique au Motorola 680x0
L'utilisation du commutateur -O2 (par défaut) effectue plusieurs optimisations dans le code produit, la plus notable étant :
- L'extension de signe de Byte à Long utilisera EXTB.
- Le retour des fonctions utilisera RTD.
- La vérification de l'intervalle ne générera aucun appel à l'exécution.
- La multiplication utilisera l'instruction longue MULS, aucun appel à la bibliothèque d'exécution ne sera généré.
- La division utilisera l'instruction DIVS longue, aucun appel à la bibliothèque d'exécution ne sera généré.
Commutateurs d'optimisation
C'est ici que sont décrits les différents interrupteurs d'optimisation et leurs actions, regroupés par interrupteur :
Commutateur | Description |
---|---|
-On: | Avec n = 1..4 : ces interrupteurs activent l'optimiseur. Un niveau supérieur inclut automatiquement tous les niveaux inférieurs.
|
-OaX=Y | Définissez l'alignement de X sur Y. |
-Oo[NO XXX | Activer ou désactiver des optimisations spécifiques. |
-OpXXX | Définissez le processeur cible pour l'optimisation sur XXX ; voir fpc -i ou fpc -ic pour les valeurs possibles. |
-OWXXX | Générez des commentaires d'optimisation de l'ensemble du programme pour l'optimisation XXX, voir fpc -i ou fpc -iw pour les valeurs possibles. |
-OwXXX | Effectuer l'optimisation de l'ensemble du programme XXX ; voir fpc -i ou fpc -iw pour les valeurs possibles. |
-Os | Optimisez pour la taille plutôt que pour la vitesse. |
Conseils pour obtenir du code plus rapidement
Ici, quelques conseils généraux pour obtenir un meilleur code sont présentés. Ils concernent principalement le style de codage.
- Trouver un meilleur algorithme. Peu importe à quel point vous et le compilateur modifiez le code, un tri rapide surpassera (presque) toujours un tri à bulles, par exemple.
- Utilisez des variables de la taille native du processeur pour lequel vous écrivez. Il s'agit actuellement de 32 bits ou 64 bits pour Free Pascal, il est donc préférable d'utiliser des variables de mots longs et d'entiers longs.
- Allumez l'optimiseur.
- Écrivez vos instructions if/then/else de manière à ce que le code dans la partie «then» soit exécuté la plupart du temps (améliore le taux de prédiction de saut réussie).
- N'utilisez pas de chaînes de caractères AnsiString, de chaînes de caractères Unicode et de prise en charge des exceptions, car celles-ci nécessitent beaucoup de temps système de code.
- Profilez votre code (voir le commutateur -pg) pour découvrir où se trouvent les goulots d'étranglement. Si vous le souhaitez, vous pouvez réécrire ces pièces en assembleur. Vous pouvez prendre le code généré par le compilateur comme point de départ. Lorsqu'on lui donne le commutateur de ligne de commande -a, le compilateur n'effacera pas le fichier assembleur à la fin du processus d'assemblage, vous pourrez donc étudier le fichier assembleur.
Conseils pour obtenir un code plus petit
Voici quelques conseils donnés pour obtenir le plus petit code possible.
- Trouver un meilleur algorithme.
- Utilisez le commutateur du compilateur -Os.
- Regroupez dans un même module les variables statiques globales ayant la même taille pour minimiser le nombre de directives d'alignement (ce qui augmente inutilement les sections .bss et .data). En interne, cela est dû au fait que toutes les données statiques sont écrites dans le fichier assembleur, dans l'ordre où elles sont déclarées dans le code source pascal.
- N'utilisez pas le modificateur cdecl, car cela génère environ 1 instruction supplémentaire après chaque appel de sous-programme.
- Utilisez les options de liaison intelligente pour toutes vos unités (y compris l'unité System).
- N'utilisez pas de chaînes de caractères AnsiString, de chaînes de caractères UniCode et de prise en charge des exceptions, car celles-ci nécessitent beaucoup de temps système de code.
- Désactivez la vérification de la portée et la vérification de la pile.
- Désactivez la génération d'informations sur le type d'exécution.
Optimisation de l'ensemble du programme
Aperçu
Traditionnellement, les compilateurs optimisent un programme procédure par procédure, ou au mieux unité de compilation par unité de compilation. L'optimisation du programme complet (WPO) signifie que le compilateur prend en compte toutes les unités de compilation composant un programme ou une bibliothèque et les optimise en utilisant la connaissance combinée de la façon dont elles sont utilisées ensemble dans ce cas particulier.
La manière dont WPO fonctionne généralement est la suivante :
- Le programme est compilé normalement, avec une option permettant d'indiquer au compilateur qu'il doit entreposer diverses informations dans un fichier de feedback.
- Le programme est recompilé une seconde fois (et éventuellement toutes les unités qu'il utilise) avec WPO activé, fournissant le fichier de retour généré lors de la première étape comme entrée supplémentaire au compilateur.
C'est le schéma suivi par Free Pascal.
La mise en oeuvre de ce schéma dépend fortement du compilateur. Une autre implémentation pourrait être que le compilateur génère une sorte de code intermédiaire (par exemple, du code d'octet) et que l'éditeur de liens effectue tous les WPO ainsi que la traduction vers le code machine cible.
Principes généraux
Quelques principes généraux ont été suivis lors de la conception de la mise en ouvre FPC de WPO :
- Toutes les informations nécessaires pour générer un fichier de commentaires WPO pour un programme sont toujours entreposées dans les fichiers ppu. Cela signifie qu'il est possible d'utiliser un RTL générique pour WPO (ou, en général, n'importe quelle unité compilée). Cela signifie que le RTL lui-même ne sera alors pas optimisé, le code du programme compilé et ses unités pourront être correctement optimisés car le compilateur sait tout ce qu'il doit savoir sur toutes les unités RTL.
- Le fichier de commentaires WPO généré est en texte brut. L'idée est qu'il devrait être facile d'inspecter ce fichier manuellement et d'y ajouter des informations produites par des outils externes si vous le souhaitez (par exemple, des informations de profil).
- L'implémentation du sous-système WPO dans le compilateur est très modulaire, il devrait donc être facile de connecter des fournisseurs d'informations WPO supplémentaires ou de choisir au moment de l'exécution entre différents fournisseurs d'informations pour le même type d'informations. Dans le même temps, l'interaction avec le reste du compilateur est réduite au strict minimum pour améliorer la maintenabilité.
- Il est possible de générer un fichier de feedback WPO tout en en utilisant un autre en entrée. Dans certains cas, utiliser ce deuxième fichier de feedback comme entrée lors d'une troisième compilation peut encore améliorer les résultats.
Comment l'utiliser
Étape 1 : Générer un fichier de commentaires WPO
La première étape dans WPO consiste à compiler le programme (ou la bibliothèque) et toutes ses unités comme cela se ferait normalement, mais en spécifiant en plus les 2 options suivantes sur la ligne de commande :
-FW/path/to/feedbackfile.wpo -OWselected_wpo_options |
La première option indique au compilateur où le fichier de commentaires WPO doit être écrit, la deuxième option indique au compilateur d'activer les optimisations WPO.
Le compilateur collectera alors, juste après que le programme ou la bibliothèque ait été lié, toutes les informations nécessaires pour exécuter les options WPO demandées lors d'une compilation ultérieure, et stockera ces informations dans le fichier indiqué.
Étape 2 : Utilisez le fichier de commentaires WPO généré
Pour appliquer réellement les options WPO, le programme (ou la bibliothèque) et tout ou partie des unités qu'il utilise, doivent être recompilés à l'aide de l'option :
-Fw/path/to/feedbackfile.wpo -Owselected_wpo_options |
(Notez les petites majuscules dans le w). Cela indiquera au compilateur d'utiliser le fichier de commentaires généré à l'étape précédente. Le compilateur lira ensuite les informations collectées sur le programme lors de l'exécution précédente du compilateur et les utilisera lors de la compilation en cours des unités et/ou du programme/bibliothèque.
Les unités non recompilées lors de la deuxième passe ne seront évidemment pas optimisées, mais elles fonctionneront toujours correctement lorsqu'elles seront utilisées avec les unités et le programme/bibliothèque optimisés.
Remarque : Notez que les options doivent toujours être spécifiées sur la ligne de commande : il n'y a pas de directive source pour activer WPO, car il n'a de sens que d'utiliser WPO lors de la compilation d'un programme complet.
Optimisations WPO disponibles
Les options de ligne de commande -OW et -Ow nécessitent une liste d'options d'optimisation de l'ensemble du programme, séparées par des virgules. Ce sont des chaînes de caractères, chaque chaîne de caractères désigne une option. Voici une liste des options disponibles :
Option | Description | |
---|---|---|
all | Cela permet toutes les optimisations disponibles du programme entier. | |
devirtcalls | Transforme les appels de méthode virtuelle en appels de méthode normaux (statiques) lorsque le compilateur peut déterminer qu'un appel de méthode virtuelle ira toujours
à la même méthode statique. Cela rend ce code à la fois plus petit et plus rapide. En général, il s'agit principalement d'une optimisation permettant d'autres optimisations,
car elle rend le programme plus facile à analyser du fait qu'elle réduit le flux de contrôle indirect. Il y a 2 limitations à cette option :
|
|
optvmts | Cette optimisation examine quels types de classes peuvent être instanciés et quelles méthodes virtuelles peuvent être appelées dans un programme, et sur la base de
ces informations, elle remplace les entrées de la table de méthodes virtuelles (VMT) ne pouvant jamais être appelées par des références à FPC_ABSTRACTERROR. Cela signifie
que de telles méthodes, à moins qu'elles ne soient appelées directement via un appel hérité d'une classe/objet enfant, peuvent être supprimées par l'éditeur de liens.
Cela a peu ou pas d'effet sur la vitesse, mais peut aider à réduire la taille du code. Cette option présente 2 limitations :
| |
wsymbolliveness | Ce paramètre n'effectue aucune optimisation à lui seul. Il indique simplement au compilateur d'enregistrer quelles fonctions/procédures ont été conservées par l'éditeur
de liens dans le programme final. Lors d'une passe wpo ultérieure, le compilateur peut alors ignorer les fonctions/procédures supprimées en ce qui concerne WPO (par
exemple, si un type de classe particulier n'est construit que dans une procédure inutilisée, alors ignorer cette procédure peut améliorer l'efficacité des deux précédentes
optimisations). Encore une fois, il existe certaines limites :
|
Format du fichier WPO
Ces informations sont particulièrement intéressantes si des données externes doivent être ajoutées au fichier de feedback WPO, par exemple à partir d'un outil de profilage. Pour une utilisation régulière de la fonctionnalité WPO, les informations suivantes ne sont pas nécessaires et peuvent être ignorées.
Le fichier est composé de commentaires et d'un certain nombre de sections. Les commentaires sont des lignes commençant par un #. Chaque section commence par "%" suivi du nom de la section (par exemple,% contextinsensitive_devirtualization).
Après cela, jusqu'à la fin du fichier ou jusqu'à la ligne suivante commençant par "%", suit d'abord une description lisible par l'homme du format de cette section (dans les commentaires), puis du contenu de la section elle-même.
Il n'y a pas de règles sur l'apparence du contenu d'une section, sauf que les lignes commençant par # sont réservées aux commentaires et les lignes commençant par % sont réservées aux marqueurs de section.