[9] Fonctions inline (intégrées)

(Une partie de C++ FAQ Lite fr, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)

Traduit de l'anglais par Jérôme Lecomte

Les FAQs de la section [9]


[9.1] Quel est le contrat avec les fonctions inline?

Quand un compilateur inline un appel a une fonction inline, le code de la fonction est insere dans le code de la fonction appelante (conceptuellement c'est similaire a ce qui se passe avec une macro #define). Cela peut, dependant de millions d'autres parametres, ameliore les performances car l'optimizer peut integrer le code appele — optimize le code appele dans le code appelant.

Il y a plusieurs facons differentes de marquer une fonction inline, l'une d'elle utilise le keyword inline, d'autres pas. Quelquesoit la facon dont vous marquez une fonction inline, c'est un conseil que le compilateur peut decider d'ignorer: il a le droit de dupliquer toutes, aucunes ou quelques appels a cette fonction inline. (Ne soyer pas decourager si cela vous semble vague. Cette flexibilite est en fait un gros avantage: cela laisse le compilateur traite les grosses fonctions differemment des petites, de plus le compilateur peut generer du code plus facile a debugger si vous choisissez les bonnes options de compilation).

[ Haut | Bas | Rechercher ]


[9.2] Quel pourrais etre un exemple de fusion de procedure ?

Voyons l'appel a la fonction g():

void f()
{
	int x = /*...*/;
	int y = /*...*/;
	int z = /*...*/;
	...code that uses x, y and z...
	g(x, y, z);
	...more code that uses x, y and z...
}

Imaginons une implementation typique du C++ avec des registres et une pile, les registres et parametres sont ecrit sur la pile avant l'appel a g(), puis les parametres sont lus de la pile dans g() et lus encore pour restaurer les registres quand g() retourne vers f(). Mais c'est beaucoup de lecture/ecriture pour rien, surtout dans les cas ou le compilateur peut utiliser les registres pour les variables x, y et z.

void g(int x, int y, int z)
{
	...code that uses x, y and z...
}

Si le compilateur peut inliner l'apple a g(), toutes operations en memoire peuvent etre elimines. Les registres n'ont plus besoin d'etre ecrit ni lu puisqu'il n'y a plus d'appel de fonction et les parametres n'ont plus besoin d'etre ecrit ni lu puisque le compilateur sait qu'ils sont deja dans les registres.

Bien le resultat va dependre selon les cas et il y a des millions de variables a prendre en compte qui sont en dehors de cette FAQ mais que cet exemple vous serve pour illustrer le genre de chose qui peuvent etre realiser.

[ Haut | Bas | Rechercher ]


[9.3] Est-ce que les fonctions inline améliorent les performances?

Oui et non. Des fois. Peut etre.

Il n'y a pas de reponses simples. Les fonctions inline peuvent rendre le code plus rapide, elles peuvent aussi le rendre plus lent. Elles peuvent rendre l'executable plus gros, elles peuvent aussi le rendre plus petit. Elles peuvent 'trasher' le system, elles peuvent aussi empecher de 'trasher'. Et elles peuvent, souvent, n'avoir aucun impact sur la vitesse.

Les fonctions inline peuvent rendre le systeme plus rapide: Comme montre ci-dessus, la fusion de procedure peut enlever un paquet d'operations inutiles, ce qui peut rendre les choses plus rapide.

Les fonctions inline peuvent rendre le systeme plus lent: Trop de fonctions inlines peut augmenter la taille du code, ce qui peut avoir comme consequence que le systeme 'trashe' sur un syteme a memoire virtuelle. C'est a dire que si l'executable devient trop gros, le systeme peux passer la majorite de son temps a lire des bouts de code du disque vers la memoire.

Les fonctions inline peuvent rendre le systeme plus gros: C'est le probleme de 'code bloat', telle que decrite plus haut. Par exemple, si un system a 100 fonctions inline avec une taille de 100 bytes de code et que ces fonctions sont appeles a 100 endroits differents, cela va conduire a une augmentation de 1MB de l'executable. Est ce que ce 1MB va causer des problemes? Qui sait, mais il est possible que ce dernier 1MB va faire que le systeme va 'trasher' et ralentir l'execution.

Les fonctions inline peuvent rendre le systeme plus petit: Le compilateur genere souvent plus de code pour faire les push/pop des registres/parametres que pour remplacer le corps des fonctions inlines. Cela arrive pour des fonctions qui sont tres petites ou tres longues quand le compilateur peut enlever du code qui est devenu redondant apres la fusion des procedures.

Les fonctions inline peuvent faire trasher: Puisque les fonctions inline peuvent augmenter la taille de l'executable, cela faire trasher le systeme.

inline functions can prevent thrashing: When f() calls g(), the code might be on two distinct pages of memory, but when the compiler procedurally integrates the code of g() into f(), they are often on the same page, so the working set size (number of pages that need to be in memory at once) may go down even if the executable size goes up.

Les fonctions inline n'ont aucun impact sur les performances: La plupart des systemes ne sont pas limites par la vitesse du processeur. Ils sont soit limites par les I/O, les acces database ou le reseau. A moins que votre "CPU chronmetre" is bloque a 100%, les fonctions inline ne vont probablement pas rendre votre systeme plus rapide. (Meme avec un systeme qui est limite par le CPU, les fonctions inline n'aideront que si elles sont utilisees sur le point critique du code et ce point critique ne concerne qu'un petit pourcentage du code.)

Il n'y a pas de reponses simples: Il faut jouer avec le systeme pour voir ce qui donne le meilleur resultat. Ne vous baser pas sur des regles trop simples telles que "ne jamais utiliser des fonctions inline" ou "toujours utiliser des fonctions inline" ou "ne mettre inline que des fonctions qui font moins que N lignes". Ce genre de regle generale donne toujours des resultats sub-optimal.

[ Haut | Bas | Rechercher ]


[9.4] Comment les fonctions inline aident à améliorer le compromis entre sûreté et vitesse?
En C pur, vous pouvez réaliser des "structs (structures) encapsulées" en mettant un void *dans un struct, où le void *pointe sur les vraies données qui sont inconnues aux utilisateurs du struct. Ainsi ces même utilisateurs du struct ne peuvent pas interpréter les données pointées par void *, mais les fonctions d'accès peuvent elles convertir du void *vers le type de données caché. Ce qui donne une forme de l'encapsulation.

Malheureusement ceci impose de renoncer à la sûreté de type, et impose également un appel de fonction pour accéder même aux zones insignifiantes du struct (si vous permettiez l'accès direct au champs du struct, chacun pourrait accèder directement à tout le struct puisqu'il connaîtrait nécessairement les données pointées par void *, et il deviendrait difficile de changer la structure de données sous-jacentes).

Le temps passé dans l'en-tête d'appel de fonction est court, mais il s'ajoute à chaque appel. Les classes C++ permettent à ces appels de fonction d'être insérés inline. Ceci vous laisse la sûreté de l'encapsulation avec en plus la vitesse des accès directs. En outre, les types des paramêtres de ces fonctions inline sont contrôlées par le compilateur, ce qui est une amélioration par rapport aux macros #define du C.

[ Haut | Bas | Rechercher ]


[9.5] Pourquoi devrais-je utiliser les fonctions inline? et non simplement les vieilles macros #define?
Parce que les macros #definesont mauvaises.

Contrairement aux macros #define, les fonctions inline évitent les erreurs infâmes liées aux macros puisque les fonctions inline évaluent toujours chaque argument une fois exactement. En d'autres termes, appeler une fonction inline est sémantiquement équivalent à appeler une fonction régulière, mais plus rapidement:

// Une macro qui retourne la valeur abolue de i
#define unsafe(i) \
( (i) >= 0 ? (i) : -(i) )

// Une fonction inline qui retourne la valeur absolue de i
inline
int safe(int i)
{
	return i >= 0 ? i : -i;
}

int f();

void codeUtilisateur(int x)
{
	int rep;

	rep = unsafe(x++); // Erreur! x est incremente deux fois
	rep = unsafe(f()); // Danger! f() is appele deux fois

	rep = safe(x++); // Correct! x est incremente une fois
	rep = safe(f()); // Correct! f() est appele une fois
}

Egalement au crédit des fonctions inline par rapport aux macros #define, les types d'argument sont contrôlés, et les conversions nécessaires sont exécutées correctement.

Les macros #define sont mauvaises pour votre santé; ne les utilisez pas à moins d'y être contraint.

[ Haut | Bas | Rechercher ]


[9.6] Comment dites-vous au compilateur de mettre une fonction non membre inline?
Quand vous déclarez une fonction inline, elle a exactement l'aspect d'une fonction normale:
void f(int i, char c);

Mais quand vous définissez une fonction inline, vous ajoutez au début de la définition de la fonction le mot-clé inline, et vous mettez la définition dans le fichier d'en-tête:

inline
void f(int i, char c)
{
	...
}

Note: Il est impératif que la définition de la fonction (la partie entre {...}) soit placé dans un fichier d'en-tête, à moins que la fonction soit utilisée seulement dans ce seul fichier .cpp. En particulier, si vous mettiez inline la définition de la fonction dans un fichier cpp et vous l'appeliez d'un autre fichier, vous obtiendriez "une erreur externe" (fonction non définie) au moment de l'édition de liens.

[ Haut | Bas | Rechercher ]


[9.7] Comment dites vous au compilateur de considérer une fonction membre inline?
Quand vous déclarez inlineune fonction membre, elle a exactment l'aspect d'une fonction membre normale :
class Fred {
public:
	void f(int i, char c);
};

Mais quand vous définissez une fonction membre inline, vous ajoutez au début de la définition de la fonction membre le mot-clé inline, et vous mettez la définition dans un fichier d'en-tête:

inline
void Fred::f(int i, char c)
{
	...
}

Il est habituellement impératif que la définition de la fonction (la partie entre {...}) soit placé dans un fichier d'en-tête. Si vous mettiez inline la définition d'une fonction dans un fichier d'implémentation cpp, et si cette fonction était appellée d'un autre fichier cpp, vous obtiendriez "une erreur externe" (fonction non définie) au moment de l'édition de liens.

[ Haut | Bas | Rechercher ]


[9.8] Y a-t-il un autre moyen de spécifier une fonction membre inline?
Oui: définissez la fonction de membre dans le corps de classe elle-même:
class Fred {
public:
	void f(int i, char c)
	{
		...
	}
};

Bien que ce soit plus facile pour la personne qui écrit la classe, c'est aussi plus dur pour le lecteur puisqu'on mélange "ce que fait" la classe avec "comment elle le fait". En raison de ce mélange, on préfère normalement définir des fonctions de membre en dehors du corps de classe avec le mot-clé inline. Comprenez que dans un monde orienté objet et réutilisation, il y a beaucoup de gens qui utilise la classe, mais une seule personne qui la créé (vous même); c'est pourquoi vous devriez faire les choses en faveurs du plus grand nombre plutôt que pour le plus petit.

[ Haut | Bas | Rechercher ]


E-mail Marshall Cline Ecrire à l'auteur, au traducteur, ou en savoir plus sur la traduction.
C++ FAQ Lite fr | Table des matières | Index | A propos de l'auteur | © | Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:54:23 PDT 2003