"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Le mot-clé obscur « restreindre » en C

Le mot-clé obscur « restreindre » en C

Publié le 2024-11-04
Parcourir:470

The Obscure “restrict” Keyword in C

Introduction

Entre autres choses, C99 a ajouté le mot-clé restrict pour permettre à 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.

Le problème

Pour illustrer le problème que restrict était censé résoudre, considérons une fonction telle que :

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 seraient 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é.

La solution

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 qu'aucun autre pointeur dans les mêmes alias de portée il.

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;
}

(Lire de droite à gauche, par exemple, v est un pointeur restreint vers un entier constant ; ou utiliser cdecl.)

En ajoutant une restriction, 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 de destination ne ne se chevauchent pas. La fonction memmove(), légèrement plus lente, existe pour être utilisée lorsque les adresses do se chevauchent.

Pièges

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 concerne une portée donnée. L'attribution d'un pointeur restreint à un autre dans la même portée entraîne un comportement non dé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
  }
}

Quand (et quand ne pas) utiliser la restriction

Tout d'abord, vous devez absolument profiler votre code (et peut-être même examiner le code d'assemblage 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 à la moitié_1ère[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 autant que vous sachiez, l'appelant s'est erreur et a transmis des pointeurs qui alias.

Divers

Seuls les pointeurs vers des objets (ou void) 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 les données seront le seul pointeur vers ces données et que la gauche et la 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 :

  • Cela peut être une source de bugs difficiles à trouver que le comité C n'a pas voulu importer depuis C.
  • L'utilisation accrue de pointeurs par C, par exemple ceci, rend l'utilisation de restrict en toute sécurité encore plus difficile.

Cependant, de nombreux compilateurs ont __restrict__ comme extension.

Conclusion

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.

À utiliser judicieusement.

Déclaration de sortie Cet article est reproduit sur : https://dev.to/pauljlucas/the-obscure-restrict-keyword-in-c-2541 En cas de violation, veuillez contacter [email protected] pour le supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3