Whole Program Optimization/fr

From Lazarus wiki
Revision as of 13:15, 15 December 2020 by E-ric (talk | contribs) (Format de fichier de rétroaction WPO)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

English (en) français (fr)

Généralités

Traditionnellement, les compilateurs optimisent un programme procédure par procédure, ou au mieux unité de compilation par unité de compilation.

L'optimisation globale de programme, en abrégé OGP, signifie que le compilateur considère toutes les unités de compilation qui compose un programme ou une bibliothèque et les optimise en utilisant une connaissance approfondie de comment ils sont utilisés ensemble dans un cas particulier.

La manière dont fonctionne l'OGP en général est comme suit :

  • vous compilez le programme normalement, en disant au compilateur d'enregistrer divers bits d'information dans un fichier de rétroaction.
  • vous recompilez the programme (et facultativement toutes les unités qu'il utilise) avec les OGP, en fournissant le fichier de rétroaction comme une entrée supplémentaire au compilateur.

Dans cetaines implémentations, le compilateur génère que sorte de code d'intermédiaire (p.ex., le byte code) et l'éditeur de lien réalise toutes les OGP tout au long de la traduction vers la cible ISA (NdT acronyme non compris). Dans le cas de FPC pourtant, le schéma suivi est celui décrit plus bas.

Principes généraux

Quelques principes généraux ont été suivis lors de la conception de l'implémentation par FPC de l'OGP:

  • Toute l'information nécessaire pour générer le fichier de rétroaction OGP pour un programme est toujours enregistré dans les fichiers PPU. Ce qui signifie que vous pouvez utiliser une RTL pour l'OGP (même si la RTL elle-même ne sera alors pas optimisée, votre programme et ses unités peuvent être correctement optimisés car le compilateur sait tout ce qu'il doit savoir sur toutes les unités RTL);
  • Le fichier de rétroaction OGP généré est du texte brut. L'idée est qu'il devrait facile d'inspecter ce fichier à la main et d'y ajouter de l'information produite par des outils externes si désiré (p.exe. des informations de profilage);
  • L'implémentation du sous-système OGP dans le compilateur est très modulaire, donc il devrait être facile d'y adjoindre des fournisseurs d'informations OGP additionnels ou de choisir à l'exécution entre différents forunisseurs d'informations pour la même sorte d'information. En 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 rétroaction WPO tout en en utilisant un autre comme entrée. Dans certains cas, l'utilisation de ce deuxième fichier de rétroaction comme entrée lors d'une troisième compilation peut encore améliorer les résultats.

Comment utiliser

Générer le fichier de rétroaction WPO (WPO feedback file)

En tout premier, compilez votre programme (ou bibliothèque) et toutes ses unités comme vous le feriez normalement, mais en compilant, vous ajoutez

-FW/path/to/feedbackfile.wpo -OW<selected_wpo_options>

.

Le compilateur va alors, juste après que votre programme a été lié, collecter toutes les informations nécessaires pour effectuer les OGP demandés lors d'une compilation successive, et stocker ces informations dans /path/to/feedbackfile.wpo.

Utiliser le fichier de rétroaction WPO

Pour appliquer réellement les WPO, recompilez le programme/bibliothèque et tout ou partie des unités qu'il utilise en employant

-Fw/path/to/feedbackfile.wpo -Ow<selected_wpo_options>

, pointant ainsi le compilateur vers le fichier de rétroaction généré à l'étape précédente.

Le compilateur lira alors l'information collectée sur le programme durant la précédente compilation, et l'utilise durant la compilation en cours des unités et/ou du programme/bibliothèque. Les unités non recompilées durant cette seconde passe ne seront bien évidemment pas optimisées, mais elle fonctionneront encore normalement si elle sont utilisées avec les unités et programme/bibliothèque optimisées.

Exemple concret

L'exemple ci-dessous fait référence à twpo4.pp.

Compilez le programme une première fois, en collectant les rétroactions dans twpo4-1.wpo dans le répertoire courant.

Le compilateur enregistrera les classes créées dans le programme (tchild1 et tchild2), et après l'étape de liaison également que notcalled n'est en fait pas appelée.

Ensuite, enregistrez le code assembleur généré dans twpo4.s1 pour une comparaison ultérieure.

$ ppn37 -FWtwpo4-1.wpo -OWall -CX -XX -Xs- -al twpo4
Free Pascal Compiler version 2.3.1 [2008/12/11] for i386
Copyright (c) 1993-2008 by Florian Klaempfl
Target OS: Darwin for i386
Compiling twpo4.pp
Assembling program
Linking twpo4
66 lines compiled, 0.6 sec
$ mv twpo4.s twpo4.s1

Maintenant compilez le programme une seconde fois, en utilisant l'information dans twpo4-1.wpo et en collectant de nouvelles informations dans twpo4-2.wpo.

Arrivé à ce point, le compilateur sait qu'aucune instance de la classe tbase n'est créée dans le programme et par conséquent il remplace toutes les entrées normales dans sa table de méthodes virtuelles par des références à FPC_ABSTRACTERROR. Les méthodes de classes virtuelles ne peuvent pas être éliminées de cette façon, puisqu'elles peuvent être appelées sans construire d'instance de ce type.

Parce que le compilateur connaît depuis le précédent fichier de rétroaction généré que la procédure notcalled n'est jamais appelée, il enregistrera aussi que seule une instance de tchild1 est créée, comme l'instance tchild2 était seulement créée dans cette procédure notcalled. À des fins d'optimisation, il considérera cependant toujours qu'une instance de tchild2 peut être créée, car à des fins d'optimisation, il utilise toujours le fichier de rétroaction généré lors de la compilation précédente .

Ensuite, nous enregistrons à nouveau le code assembleur généré pour des comparaisons ultérieures.

$ ppn37 -FWtwpo4-2.wpo -OWall -Fwtwpo4-1.wpo -Owall -CX -XX -Xs- -al twpo4
Free Pascal Compiler version 2.3.1 [2008/12/11] for i386
Copyright (c) 1993-2008 by Florian Klaempfl
Target OS: Darwin for i386
Compiling twpo4.pp
Assembling program
Linking twpo4
66 lines compiled, 0.1 sec
$ mv twpo4.s twpo4.s2

Compilez le programme une dernière fois, en utilisant les informations collectées dans twpo4-2.wpo. Cette fois, le compilateur sait que seul une instance de tchild1 est créée, et va par conséquent remplacer l'appel bb.test depuis une méthode virtuelle par une méthode statique (car il sait que même si le type de bb est tbase, en pratique il ne peut être qu'un tchild1 car aucune instance de tbase ou autre instance de classe descendant de tbase n'est créée.

$ ppn37 -Fwtwpo4-2.wpo -Owall -CX -XX -al twpo4
Free Pascal Compiler version 2.3.1 [2008/12/11] for i386
Copyright (c) 1993-2008 by Florian Klaempfl
Target OS: Darwin for i386
Compiling twpo4.pp
Assembling program
Linking twpo4
66 lines compiled, 0.1 sec

Maintenant, regardons les différences entre les fichiers assembleur générés. Après la première recompilation :

$ diff -u twpo4.s1 twpo4.s2
--- twpo4.s1	2008-12-11 18:59:55.000000000 +0100
+++ twpo4.s2	2008-12-11 19:00:15.000000000 +0100
@@ -214,15 +214,15 @@
 	.long	0,0,0
 	.long	FPC_EMPTYINTF
 	.long	0
-	.long	_SYSTEM_TOBJECT_$__DESTROY
+	.long	FPC_ABSTRACTERROR
 	.long	_SYSTEM_TOBJECT_$__NEWINSTANCE$$TOBJECT
-	.long	_SYSTEM_TOBJECT_$__FREEINSTANCE
-	.long	_SYSTEM_TOBJECT_$__SAFECALLEXCEPTION$TOBJECT$POINTER$$LONGINT
-	.long	_SYSTEM_TOBJECT_$__DEFAULTHANDLER$formal
-	.long	_SYSTEM_TOBJECT_$__AFTERCONSTRUCTION
-	.long	_SYSTEM_TOBJECT_$__BEFOREDESTRUCTION
-	.long	_SYSTEM_TOBJECT_$__DEFAULTHANDLERSTR$formal
-	.long	_P$PROGRAM_TBASE_$__TEST
+	.long	FPC_ABSTRACTERROR
+	.long	FPC_ABSTRACTERROR
+	.long	FPC_ABSTRACTERROR
+	.long	FPC_ABSTRACTERROR
+	.long	FPC_ABSTRACTERROR
+	.long	FPC_ABSTRACTERROR
+	.long	FPC_ABSTRACTERROR
 	.long	0

 .const_data

Comme vous pouvez le voir, toutes les références aux méthodes virtuelles dans la VMT de tbase ont été retirées, puisque le compilateur sait qu'elles ne peuvent pas être réféencées. Cela permet à l'éditeur de liens de jeter ces méthodes si elles ne sont référencées nulle part ailleurs non plus.

Dans notre exemple, c'est seulement vrai pour tbase.test.

Maintenant regardons l'effet de la seconde recompilation:

$ diff -u twpo4.s2 twpo4.s
--- twpo4.s2	2008-12-11 19:00:15.000000000 +0100
+++ twpo4.s	2008-12-11 19:00:29.000000000 +0100
@@ -103,9 +103,7 @@
 	movl	%eax,-4(%ebp)
 # [54] bb.test;
 	movl	-4(%ebp),%eax
-	movl	-4(%ebp),%edx
-	movl	(%edx),%edx
-	call	*80(%edx)
+	call	L_P$PROGRAM_TCHILD1_$__TEST$stub
 # [55] bb.free;
 	movl	-4(%ebp),%eax
 	call	L_SYSTEM_TOBJECT_$__FREE$stub
@@ -139,10 +137,7 @@
 # [63] bb.test;
 	movl	L_U_P$PROGRAM_BB$non_lazy_ptr-Lj2(%ebx),%eax
 	movl	(%eax),%eax
-	movl	L_U_P$PROGRAM_BB$non_lazy_ptr-Lj2(%ebx),%edx
-	movl	(%edx),%edx
-	movl	(%edx),%edx
-	call	*80(%edx)
+	call	L_P$PROGRAM_TCHILD1_$__TEST$stub
 # [64] a:=2;
 	movl	L_U_P$PROGRAM_A$non_lazy_ptr-Lj2(%ebx),%eax
 	movl	$2,(%eax)

Comme vous pouvez le voir, les deux appels de méthode virtuelle à bb.test (un dans la procédure notcalled, jamais appelée, et un dans le programme principal) ont été remplacé par des appels à tchild1.test.

Remarquez que vous pourriez aussi recompiler la RTL pour ce programme particulier, si vous voulez.

Quand utiliser

Puisque l'optimisation globale de programme demande de multiples compilation, il est recommandable de limiter son utilisation aux versions de livraison finale.

Gardez aussi à l'esprit qu'une fois que l'unité a été compilée en utilisant wpo pour un programme particulier, elle doit être à nouveau compilée pour un autre programme.

Optimisation disponible de programme entier

Toutes les optimisations

paramètre
-OWall/-Owall
effet
Active toutes les optimisations globlales de programme décrites ci-dessus.
limitations
Les limitations de toutes les optimisations décrites ci-dessus.

Dévirtualisation de programme entier

paramètre
-OWdevirtcalls/-Owdevirtcalls
effet
Transforme les appels de méthode virtuelle en appels de méthode statique quand le compilateur peut déterminer que l'appel de méthode virtuelle ira toujours à la même méthode statique. Cela rend le code plus petit et plus rapide. En général, il s'agit principalement d'une optimisation permettant d'autres optimisations, car elle facilite l'analyse du programme en réduisant le flux de contrôle indirect.
limitations
  • L'implémentation actuelle est insensible au contexte. Cela signifie que le compilateur ne regarde que le programme dans son ensemble et détermine pour chaque type de classe quelles méthodes peuvent être dévirtualisées, plutôt que de regarder chaque instruction d'appel et le code environnant pour déterminer si cet appel peut être dévirtualisé ou non;
  • L'implémentation actuelle ne dévirtualise pas encore les appels de méthode d'interface (ni lors de leur appel via une instance d'interface, ni lors de leur appel via une instance de classe).

Optimiser la table des méthodes virtuelles

paramètre
-OWoptvmts/-Owoptvmts
effet
Cette optimisation examine quels types de classe peuvent être instanciés et quelles méthodes virtuelles peuvent être appelées dans un programme, et sur la base de ces informations, il remplace les entrées de la VMT qui ne peuvent jamais être appelées avec des références vers FPC_ABSTRACTERROR. Ce qui signifie que de telles méthodes, qu'elles ne soient appelées directement via un appel inherited depuis une classe/objet enfant, peuvent être retirées par l'éditeur de lien. Cela a peu ou pas d'effet que la vitesse, mais peut aider à réduire la taille du code.
limitations
  • Les méthodes qui sont published, ou les getters/setters de propriétés publiées, ne peuvent jamais être optimisées de cette façon car elles peuvent toujours être référencées via la RTTI (ce que le compilateur ne peut pas détecter).
  • De telles optimisations ne sont pas encore faites pour les méthodes virtuelles de classe (mais cela ne devrait pas être difficile à ajouter).

Vivacité de symbole (Symbol liveness)

paramètre
-OWsymbolliveness/-Owsymbolliveness
effet
Ce paramètre ne réalise aucune optimisation en lui-même. Il dit simplement au compilateur d'enregistrer quelles fonctions/procédures seront conservées dans le programme final. Lors d'une passe wpo ultérieure, le compilateur peut alors ignorer les fonctions/procédures aussi loin que le wpo est concerné (p.ex. si une classe particulière est seulement construite dans une procédure inutilisée, alors en ignorant cette procédure, on peut améliorer l'efficacité des deux optimisations précédentes).
limitations
  • Cette optimisation demande que l'utilitaire nm(1) soit installé sur le système. Pour les binaires linux, objdump(1) marchera aussi. Dans le futur, cette information pourra être extraite aussi de l'éditeur de lien interne pour les plateformes qui le supporte.
  • La collection d'information pour cette optimisation (en utilisant -OWsymbolliveness) demande que la liaison intelligente soit activée (-XX) et que l'élimination de symbole ne le soit pas (-Xs-). Lorsque vous utilisez uniquement ces informations précédemment collectées (en utilisant -OwSymbolliveness ou -Owall), ces limitations ne s'appliquent pas.

Format de fichier de rétroaction WPO

Cette information est principalement intéressante si vous voulez ajouter des données externes au fichier de rétroaction WPO, p.ex. depuis un outil de profilage. Si vous êtes juste un utilisateur de la fonctionnalité WPO, vous pouvez ignorer ce qui suit.

Le fichier consiste en commentaires et un nombre de sections. Les commentaires sont des lignes qui commencent avec un "#". Chaque section commence avec "% " (signe pourcent et espace) suivi du nom de la section (p.ex. "% 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 le 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 pour les commentaires et les lignes commençant par "% " sont réservés aux marqueurs de section.