Maison >développement back-end >C++ >Le mot-clé obscur « restreindre » en C
Entre autres choses, C99 a ajouté le mot-clé restrict comme moyen pour un programmeur de spécifier qu'un pointeur est le seul pointeur vers un objet donné dans une portée et, par conséquent, de donner au compilateur un « indice » " qu'il peut effectuer des optimisations supplémentaires lors de l'accès à l'objet via ce pointeur.
Pour illustrer le problème que restrict était censé résoudre, considérons une fonction comme :
void update_ptrs( int *p, int *q, int const *v ) { *p += *v; *q += *v; }
pour lequel le compilateur générera du code x86-64 comme :
mov eax, [rdx] ; tmp = *v // 1 add [rdi], eax ; *p += tmp mov eax, [rdx] ; tmp = *v // 3 add [rsi], eax ; *q += tmp
Vous vous demandez peut-être pourquoi il génère la ligne 3 puisqu'elle semble redondante avec la ligne 1. Le problème est que le compilateur ne peut pas savoir que vous n'avez pas fait quelque chose comme ceci :
int x = 1, v = 2; update_ptrs( &v, &x, &v ); // x = 5, v = 4
Dans update_ptrs(), p et v alias le même int, donc le compilateur doit jouer la sécurité et supposer que la valeur de *v peut changer entre les lectures, d'où l'instruction mov supplémentaire.
En général, les pointeurs en C confondent l'optimisation puisque le compilateur ne peut pas savoir si deux pointeurs s'alias l'un l'autre. Dans le code critique en termes de performances, l'élimination des lectures de mémoire pourrait être une énorme victoire si le compilateur pouvait le faire en toute sécurité.
Pour résoudre le problème susmentionné, restrict a été ajouté à C pour vous permettre de spécifier qu'un pointeur donné est le seul pointeur vers un objet dans la portée du pointeur, c'est-à-dire aucun autre pointeur dans les mêmes alias de portée ça.
Pour utiliser restrict, vous l'insérez entre le * et le nom du pointeur dans une déclaration. Un update_ptrs() réécrit pour utiliser restrict serait :
void update_ptrs_v2( int *restrict p, int *restrict q, int const *restrict v ) { *p += *v; *q += *v; }
(Lisez de droite à gauche, par exemple, v est un pointeur restreint vers un entier constant ; ou utilisez cdecl.)
En ajoutant restrict, le compilateur peut désormais générer du code comme :
mov eax, [rdx] ; tmp = *v add [rdi], eax ; *p += tmp add [rsi], eax ; *q += tmp
Maintenant, le compilateur a pu éluder la ligne 3 précédente de l'instruction mov supplémentaire.
L'exemple le plus connu d'utilisation de restrict est peut-être la fonction de bibliothèque standard memcpy(). C'est le moyen le plus rapide de copier une partie de la mémoire si les adresses source et destination ne ne se chevauchent. La fonction memmove() légèrement plus lente existe pour être utilisée lorsque les adresses do se chevauchent.
Une mauvaise utilisation de restrict entraîne un comportement non défini, par exemple en passant des pointeurs qui font des alias les uns les autres vers update_ptrs_v2() ou memcpy(). Dans certains cas, le compilateur peut vous avertir, mais pas dans tous les cas, alors ne comptez pas sur le compilateur pour détecter les utilisations abusives.
Notez que la restriction s'applique à une portée donnée. L'attribution d'un pointeur restreint à un autre dans la même portée entraîne un comportement indéfini :
void f( int *restrict d, int *restrict s ) { int *restrict p = s; // undefined behavior
Cependant, vous pouvez très bien attribuer un pointeur restreint à un pointeur non restreint :
void f( int *restrict d, int *restrict s ) { int *p = s; // OK
Même si p n'est pas restreint, le compilateur peut toujours effectuer les mêmes optimisations.
Il est également possible d'attribuer un pointeur restreint dans une portée interne à un autre dans une portée externe (mais pas l'inverse) :
void f( int *restrict d, int *restrict s ) { { // inner scope int *restrict p = s; // OK // ... s = p; // undefined behavior } }
Tout d'abord, vous devez absolument profiler votre code (et peut-être même regarder le code assembleur généré) pour voir si l'utilisation de restrict apporte réellement une amélioration significative des performances pour justifier le risque de pièges potentiels. Diagnostiquer les bugs causés par une mauvaise utilisation de restrict est très difficile à faire.
Deuxièmement, si l'utilisation de restrict est limitée à l'implémentation d'une fonction où la mémoire accessible via des pointeurs restreints a été allouée par vous, alors c'est plus sûr. Par exemple, étant donné :
void safer( unsigned n ) { n += n % 2 != 0; // make even by rounding up int *const array = malloc( n * sizeof(unsigned) ); unsigned *restrict half_1st = array; unsigned *restrict half_2nd = array + n/2; // ... free( array ); }
le code pourrait fonctionner sur les première et deuxième moitiés du tableau en toute sécurité car elles ne se chevauchent pas (en supposant que vous n'accédiez jamais à half_1st[n/2] ou au-delà).
Troisièmement, si restrict est utilisé dans les paramètres d'une fonction, alors c'est potentiellement moins sûr. Par exemple, contrastez safer() avec update_ptrs_v2() où l'appelant contrôle les pointeurs. Pour tout ce que vous savez, l'appelant s'est trompé et a transmis des pointeurs qui alias.
Seuls les pointeurs vers des objets (ou vides) peuvent être qualifiés avec restrict :
restrict int x; // error: can't restrict object int restrict *p; // error: pointer to restrict object int (*restrict f)(); // error: pointer-to-function
Vous pouvez utiliser restrict pour les membres de la structure, par exemple :
struct node { void *restrict data; struct node *restrict left; struct node *restrict right; };
dit que data sera le seul pointeur vers ces données et que gauche et droite ne pointeront jamais vers le même nœud. Cependant, l'utilisation de restrict pour les membres d'une structure est très rare.
Enfin, C++ n'a pas de restriction. Pourquoi pas? Il y a une longue réponse, mais la version TL;DR est la suivante :
Cependant, de nombreux compilateurs ont __restrict__ comme extension.
Dans des cas limités, l'utilisation de restrict peut conduire à des améliorations de performances, mais il existe plusieurs pièges importants. Si vous envisagez d'utiliser la restriction, profilez d'abord votre code.
Utilisez judicieusement.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!